@agent-spaces/server 0.3.67 → 0.4.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.
Files changed (64) hide show
  1. package/dist/adapters/agent-runtime.js +4 -0
  2. package/dist/adapters/codex-function-tool-bridge.js +9 -1
  3. package/dist/adapters/git.js +13 -2
  4. package/dist/adapters/hermes-runtime.js +711 -108
  5. package/dist/adapters/langchain-runtime.js +384 -21
  6. package/dist/adapters/oh-my-pi-runtime.js +858 -0
  7. package/dist/adapters/open-agent-sdk-runtime.js +202 -5
  8. package/dist/adapters/open-agent-sdk-runtime.test.js +35 -0
  9. package/dist/agents/agent-message-parts.js +52 -2
  10. package/dist/agents/issue-task-controller.js +4 -2
  11. package/dist/agents/title-generator-agent.js +120 -0
  12. package/dist/app.js +59 -6
  13. package/dist/routes/agent-sse.js +70 -7
  14. package/dist/routes/channel.js +23 -2
  15. package/dist/routes/chat-run.js +191 -0
  16. package/dist/routes/chat.js +142 -0
  17. package/dist/routes/command.js +16 -0
  18. package/dist/routes/data.js +189 -0
  19. package/dist/routes/git.js +10 -1
  20. package/dist/routes/import.js +199 -0
  21. package/dist/routes/issue.js +16 -4
  22. package/dist/routes/plugin.js +69 -0
  23. package/dist/routes/version.js +2 -1
  24. package/dist/routes/workflow-hook.js +71 -0
  25. package/dist/routes/workflow.js +282 -7
  26. package/dist/routes/workspace.js +13 -4
  27. package/dist/services/agent.js +123 -36
  28. package/dist/services/ai-text.js +185 -0
  29. package/dist/services/builtin-tools/index.js +1 -0
  30. package/dist/services/builtin-tools/workflow-editor-tools.js +509 -0
  31. package/dist/services/builtin-tools/workflow-exec-tools.js +320 -0
  32. package/dist/services/chat.js +134 -0
  33. package/dist/services/command-process-manager.js +16 -0
  34. package/dist/services/execution-manager.js +1346 -0
  35. package/dist/services/generated-title.js +59 -0
  36. package/dist/services/gitignore.js +22 -18
  37. package/dist/services/interaction-manager.js +114 -0
  38. package/dist/services/issue-retry.js +25 -0
  39. package/dist/services/output-style.js +8 -1
  40. package/dist/services/plugin.js +257 -0
  41. package/dist/services/prompt-template.js +8 -1
  42. package/dist/services/pty.js +20 -0
  43. package/dist/services/search.js +16 -6
  44. package/dist/services/skill.js +2 -0
  45. package/dist/services/version.js +17 -7
  46. package/dist/services/workflow-command-runner.js +5 -4
  47. package/dist/services/workflow-trigger-service.js +137 -0
  48. package/dist/services/workflow.js +199 -36
  49. package/dist/storage/agent-store.js +8 -0
  50. package/dist/storage/chat-store.js +151 -0
  51. package/dist/storage/database-store.js +6 -0
  52. package/dist/storage/json-store.js +2 -1
  53. package/dist/storage/kanban-store.js +6 -0
  54. package/dist/storage/workflow-store.js +386 -22
  55. package/dist/ws/agent-prompt.js +6 -2
  56. package/dist/ws/agent-runner.js +29 -18
  57. package/dist/ws/chat-handler.js +89 -0
  58. package/dist/ws/connection-manager.js +49 -2
  59. package/dist/ws/execution-channels.js +88 -0
  60. package/dist/ws/handler.js +32 -5
  61. package/dist/ws/message-parts.js +130 -12
  62. package/dist/ws/terminal-handler.js +26 -0
  63. package/package.json +12 -1
  64. package/public/avatars/user.jpg +0 -0
@@ -0,0 +1,151 @@
1
+ import { getDataDir, ensureDir, readJsonFile, writeJsonFile, deleteFile } from './json-store.js';
2
+ import path from 'node:path';
3
+ import { existsSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
4
+ function chatDir() {
5
+ return path.join(getDataDir(), 'chat');
6
+ }
7
+ function chatTemplatesDir() {
8
+ return path.join(getDataDir(), 'chat-templates');
9
+ }
10
+ export function agentDir(agentId) {
11
+ return path.join(chatTemplatesDir(), agentId);
12
+ }
13
+ function agentFile(agentId) {
14
+ return path.join(agentDir(agentId), 'agent.json');
15
+ }
16
+ function mcpFile(agentId) {
17
+ return path.join(agentDir(agentId), 'mcp.json');
18
+ }
19
+ function skillsDir(agentId) {
20
+ return path.join(agentDir(agentId), 'skills');
21
+ }
22
+ export function chatSessionDir(chatId) {
23
+ return path.join(chatDir(), chatId);
24
+ }
25
+ export function chatWorkspaceDir(chatId) {
26
+ return path.join(chatSessionDir(chatId), 'workspaces');
27
+ }
28
+ function chatWorkspaceSkillsDir(chatId) {
29
+ return path.join(chatWorkspaceDir(chatId), 'skills');
30
+ }
31
+ export function messageHistoryDir(agentId) {
32
+ return chatSessionDir(agentId);
33
+ }
34
+ function messagesFile(chatId) {
35
+ return path.join(chatSessionDir(chatId), 'messages.json');
36
+ }
37
+ // --- Agent functions ---
38
+ export function listAgents() {
39
+ ensureDir(chatTemplatesDir());
40
+ return readdirSync(chatTemplatesDir(), { withFileTypes: true })
41
+ .filter(entry => entry.isDirectory())
42
+ .flatMap(entry => {
43
+ const agent = readJsonFile(agentFile(entry.name));
44
+ return agent ? [agent] : [];
45
+ })
46
+ .sort((a, b) => a.createdAt.localeCompare(b.createdAt));
47
+ }
48
+ export function saveAgent(agent, skillInputs) {
49
+ const dir = agentDir(agent.id);
50
+ ensureDir(dir);
51
+ ensureDir(skillsDir(agent.id));
52
+ ensureDir(chatWorkspaceDir(agent.id));
53
+ ensureDir(chatWorkspaceSkillsDir(agent.id));
54
+ writeJsonFile(agentFile(agent.id), agent);
55
+ writeJsonFile(mcpFile(agent.id), agent.mcps ?? {});
56
+ writeSkills(agent.id, skillInputs, agent.skills ?? []);
57
+ }
58
+ export function findAgent(id) {
59
+ return readJsonFile(agentFile(id)) ?? undefined;
60
+ }
61
+ export function deleteAgent(id) {
62
+ rmSync(agentDir(id), { recursive: true, force: true });
63
+ rmSync(chatSessionDir(id), { recursive: true, force: true });
64
+ }
65
+ // --- Message functions ---
66
+ export function listMessages(agentId, limit, before) {
67
+ ensureDir(chatSessionDir(agentId));
68
+ let messages = readJsonFile(messagesFile(agentId)) ?? [];
69
+ messages = messages.filter(msg => msg.agentId === agentId);
70
+ // Sort descending by timestamp
71
+ messages.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
72
+ // Cursor pagination: skip messages at or after 'before' timestamp
73
+ if (before) {
74
+ const idx = messages.findIndex(m => m.timestamp <= before);
75
+ if (idx === -1) {
76
+ messages = [];
77
+ }
78
+ else {
79
+ messages = messages.slice(idx);
80
+ }
81
+ }
82
+ // Apply limit
83
+ if (limit && limit > 0) {
84
+ messages = messages.slice(0, limit);
85
+ }
86
+ // Return ascending order
87
+ messages.reverse();
88
+ return messages;
89
+ }
90
+ export function saveMessage(msg) {
91
+ const messages = readJsonFile(messagesFile(msg.agentId)) ?? [];
92
+ messages.push(msg);
93
+ writeJsonFile(messagesFile(msg.agentId), messages);
94
+ }
95
+ export function deleteMessagesByAgent(agentId) {
96
+ deleteFile(messagesFile(agentId));
97
+ }
98
+ export function getRecentMessages(agentId, limit = 50) {
99
+ return listMessages(agentId, limit);
100
+ }
101
+ function writeSkills(agentId, skillInputs, skillNames) {
102
+ const dir = skillsDir(agentId);
103
+ if (skillInputs?.some(skill => typeof skill !== 'string')) {
104
+ rmSync(dir, { recursive: true, force: true });
105
+ ensureDir(dir);
106
+ for (const skill of skillInputs) {
107
+ if (typeof skill === 'string' || !skill.name.trim())
108
+ continue;
109
+ const filename = sanitizeMarkdownFilename(skill.name);
110
+ const content = skill.content ?? readSkillContent(skill.name) ?? '';
111
+ writeJsonSkillFile(path.join(dir, filename), content);
112
+ writeJsonSkillFile(path.join(chatWorkspaceSkillsDir(agentId), filename), content);
113
+ }
114
+ return;
115
+ }
116
+ for (const name of skillNames.map(skill => typeof skill === 'string' ? skill : skill.name)) {
117
+ const filename = sanitizeMarkdownFilename(name);
118
+ const content = readSkillContent(name) ?? '';
119
+ const filePath = path.join(dir, filename);
120
+ if (content || !existsSync(filePath))
121
+ writeJsonSkillFile(filePath, content);
122
+ const workspaceSkillPath = path.join(chatWorkspaceSkillsDir(agentId), filename);
123
+ if (content || !existsSync(workspaceSkillPath))
124
+ writeJsonSkillFile(workspaceSkillPath, content);
125
+ }
126
+ }
127
+ function writeJsonSkillFile(filePath, content) {
128
+ ensureDir(path.dirname(filePath));
129
+ writeFileSync(filePath, content, 'utf-8');
130
+ }
131
+ function sanitizeMarkdownFilename(name) {
132
+ const base = name.trim().replace(/[/\\:*?"<>|]+/g, '-');
133
+ return base.toLowerCase().endsWith('.md') ? base : `${base}.md`;
134
+ }
135
+ function readSkillContent(name) {
136
+ const skillName = name.trim().replace(/\.md$/i, '');
137
+ if (!skillName)
138
+ return undefined;
139
+ const candidates = [
140
+ path.join(getDataDir(), 'skills', skillName, 'SKILL.md'),
141
+ path.join(getDataDir(), 'skills', `${skillName}.md`),
142
+ path.join(process.cwd(), 'skills', skillName, 'SKILL.md'),
143
+ path.join(process.cwd(), 'skills', `${skillName}.md`),
144
+ ];
145
+ const source = candidates.find(file => existsSync(file));
146
+ if (!source)
147
+ return undefined;
148
+ const content = readFileSync(source, 'utf-8');
149
+ return content.trim() ? content : undefined;
150
+ }
151
+ //# sourceMappingURL=chat-store.js.map
@@ -362,4 +362,10 @@ export function listDatabaseEmbeddings(workspaceId, databaseId) {
362
362
  embedding: JSON.parse(row.embedding),
363
363
  }));
364
364
  }
365
+ export function closeDb() {
366
+ if (db) {
367
+ db.close();
368
+ db = null;
369
+ }
370
+ }
365
371
  //# sourceMappingURL=database-store.js.map
@@ -1,6 +1,7 @@
1
1
  import { readFileSync, writeFileSync, existsSync, mkdirSync, unlinkSync } from 'node:fs';
2
2
  import { join, dirname } from 'node:path';
3
- const DEFAULT_DATA_DIR = join(process.env.HOME || '~', '.agent-spaces-data');
3
+ import { homedir } from 'node:os';
4
+ const DEFAULT_DATA_DIR = join(process.env.HOME || process.env.USERPROFILE || homedir(), '.agent-spaces-data');
4
5
  export function getDataDir() {
5
6
  return process.env.AGENT_SPACES_DATA_DIR || DEFAULT_DATA_DIR;
6
7
  }
@@ -139,4 +139,10 @@ export function saveTasks(boardId, tasks) {
139
139
  }
140
140
  database.prepare('UPDATE kanban_boards SET updated_at = ? WHERE id = ?').run(Date.now(), boardId);
141
141
  }
142
+ export function closeDb() {
143
+ if (db) {
144
+ db.close();
145
+ db = null;
146
+ }
147
+ }
142
148
  //# sourceMappingURL=kanban-store.js.map
@@ -1,40 +1,404 @@
1
1
  // packages/server/src/storage/workflow-store.ts
2
- // NOTE: All functions are synchronous to match json-store.ts and issue-store.ts patterns
3
- import { ensureDir, readJsonFile, writeJsonFile, deleteFile, getDataDir } from './json-store.js';
2
+ // Per-workflow directory storage: supports versions, execution logs, plugin configs.
3
+ // Legacy flat-file workflows are auto-migrated on first access.
4
+ import { ensureDir, readJsonFile, writeJsonFile, getDataDir } from './json-store.js';
5
+ import { existsSync, readdirSync, rmSync, unlinkSync, readFileSync } from 'node:fs';
4
6
  import path from 'node:path';
5
- function workflowsDir() {
7
+ // ---- Path helpers ----
8
+ export function workflowsDir() {
6
9
  return path.join(getDataDir(), 'workflows');
7
10
  }
8
- function workflowsIndex() {
11
+ function workflowDir(workflowId) {
12
+ return path.join(workflowsDir(), workflowId);
13
+ }
14
+ function workflowPath(workflowId) {
15
+ return path.join(workflowDir(workflowId), 'workflow.json');
16
+ }
17
+ function workflowIndexPath() {
9
18
  return path.join(workflowsDir(), 'index.json');
10
19
  }
11
- function workflowFile(workflowId) {
20
+ function versionsDir(workflowId) {
21
+ return path.join(workflowDir(workflowId), 'versions');
22
+ }
23
+ function executionHistoryDir(workflowId) {
24
+ return path.join(workflowDir(workflowId), 'execution_history');
25
+ }
26
+ function pluginConfigsDir(workflowId) {
27
+ return path.join(workflowDir(workflowId), 'plugin_configs');
28
+ }
29
+ function pluginSchemePath(workflowId, pluginId, schemeName) {
30
+ return path.join(pluginConfigsDir(workflowId), pluginId, `${schemeName}.json`);
31
+ }
32
+ function workflowFoldersPath() {
33
+ return path.join(workflowsDir(), 'folders.json');
34
+ }
35
+ function stagingPath(workflowId) {
36
+ return path.join(workflowDir(workflowId), 'staging.json');
37
+ }
38
+ function operationHistoryPath(workflowId) {
39
+ return path.join(workflowDir(workflowId), 'operation_history.json');
40
+ }
41
+ function chatPath(workflowId) {
42
+ return path.join(workflowDir(workflowId), 'chat.json');
43
+ }
44
+ // ---- Legacy migration ----
45
+ function legacyWorkflowPath(workflowId) {
12
46
  return path.join(workflowsDir(), `${workflowId}.json`);
13
47
  }
14
- export function listWorkflows() {
15
- return readJsonFile(workflowsIndex()) ?? [];
48
+ function migrateFromLegacyFormat() {
49
+ const dir = workflowsDir();
50
+ if (!existsSync(dir))
51
+ return;
52
+ // Read legacy index
53
+ const indexPath = workflowIndexPath();
54
+ const legacyIndex = readJsonFile(indexPath);
55
+ if (!Array.isArray(legacyIndex) || legacyIndex.length === 0)
56
+ return;
57
+ // Check if already migrated (has per-workflow dirs)
58
+ const entries = readdirSync(dir, { withFileTypes: true });
59
+ const hasWorkflowDirs = entries.some(e => e.isDirectory() && existsSync(path.join(dir, e.name, 'workflow.json')));
60
+ if (hasWorkflowDirs)
61
+ return;
62
+ // Migrate each legacy workflow
63
+ for (const entry of legacyIndex) {
64
+ const legacyFile = legacyWorkflowPath(entry.id);
65
+ if (!existsSync(legacyFile))
66
+ continue;
67
+ try {
68
+ const raw = JSON.parse(readFileSync(legacyFile, 'utf-8'));
69
+ const migrated = migrateLegacyWorkflow(raw);
70
+ ensureDir(workflowDir(migrated.id));
71
+ writeJsonFile(workflowPath(migrated.id), migrated);
72
+ unlinkSync(legacyFile);
73
+ }
74
+ catch {
75
+ // Skip broken files
76
+ }
77
+ }
78
+ // Remove legacy index
79
+ unlinkSync(indexPath);
80
+ }
81
+ function migrateLegacyWorkflow(raw) {
82
+ return {
83
+ id: raw.id,
84
+ name: raw.name,
85
+ folderId: raw.folderId ?? null,
86
+ description: raw.description,
87
+ nodes: Array.isArray(raw.nodes) ? raw.nodes.map(migrateLegacyNode) : [],
88
+ edges: Array.isArray(raw.edges) ? raw.edges.map(migrateLegacyEdge) : [],
89
+ createdAt: typeof raw.createdAt === 'string' ? Date.parse(raw.createdAt) : (raw.createdAt || Date.now()),
90
+ updatedAt: typeof raw.updatedAt === 'string' ? Date.parse(raw.updatedAt) : (raw.updatedAt || Date.now()),
91
+ };
92
+ }
93
+ function migrateLegacyNode(node) {
94
+ return {
95
+ id: node.id,
96
+ type: node.type || 'agent',
97
+ label: node.data?.label || node.label || '',
98
+ position: node.position || { x: 0, y: 0 },
99
+ data: { ...node.data },
100
+ };
101
+ }
102
+ function migrateLegacyEdge(edge) {
103
+ return {
104
+ id: edge.id,
105
+ source: edge.source,
106
+ target: edge.target,
107
+ sourceHandle: edge.sourceHandle ?? null,
108
+ targetHandle: edge.targetHandle ?? null,
109
+ };
110
+ }
111
+ // ---- CRUD ----
112
+ export function listWorkflows(folderId) {
113
+ migrateFromLegacyFormat();
114
+ const items = readAllWorkflowFiles();
115
+ if (folderId !== undefined)
116
+ return items.filter(w => w.folderId === folderId);
117
+ return items;
16
118
  }
17
119
  export function getWorkflow(workflowId) {
18
- return readJsonFile(workflowFile(workflowId));
120
+ migrateFromLegacyFormat();
121
+ return readWorkflowFile(workflowId);
19
122
  }
20
123
  export function createWorkflow(workflow) {
21
- ensureDir(workflowsDir());
22
- writeJsonFile(workflowFile(workflow.id), workflow);
23
- const index = listWorkflows();
24
- index.push(workflow);
25
- writeJsonFile(workflowsIndex(), index);
124
+ ensureDir(workflowDir(workflow.id));
125
+ writeJsonFile(workflowPath(workflow.id), workflow);
26
126
  }
27
127
  export function updateWorkflow(workflow) {
28
- writeJsonFile(workflowFile(workflow.id), workflow);
29
- const index = listWorkflows();
30
- const idx = index.findIndex(w => w.id === workflow.id);
31
- if (idx !== -1)
32
- index[idx] = workflow;
33
- writeJsonFile(workflowsIndex(), index);
128
+ ensureDir(workflowDir(workflow.id));
129
+ writeJsonFile(workflowPath(workflow.id), workflow);
34
130
  }
35
131
  export function deleteWorkflow(workflowId) {
36
- deleteFile(workflowFile(workflowId));
37
- const index = listWorkflows();
38
- writeJsonFile(workflowsIndex(), index.filter(w => w.id !== workflowId));
132
+ const dir = workflowDir(workflowId);
133
+ if (existsSync(dir))
134
+ rmSync(dir, { recursive: true, force: true });
135
+ // Also clean up legacy flat file if it exists
136
+ const legacy = legacyWorkflowPath(workflowId);
137
+ if (existsSync(legacy))
138
+ unlinkSync(legacy);
139
+ }
140
+ // ---- Folders ----
141
+ export function listWorkflowFolders() {
142
+ const data = readJsonFile(workflowFoldersPath());
143
+ const folders = data?.folders ?? [];
144
+ return [...folders].sort((a, b) => a.order - b.order);
145
+ }
146
+ export function createWorkflowFolder(folder) {
147
+ const folders = listWorkflowFolders();
148
+ folders.push(folder);
149
+ writeJsonFile(workflowFoldersPath(), { folders });
150
+ }
151
+ export function updateWorkflowFolder(id, updates) {
152
+ const folders = listWorkflowFolders();
153
+ const idx = folders.findIndex(f => f.id === id);
154
+ if (idx === -1)
155
+ throw new Error(`Folder ${id} not found`);
156
+ folders[idx] = { ...folders[idx], ...updates };
157
+ writeJsonFile(workflowFoldersPath(), { folders });
158
+ }
159
+ export function deleteWorkflowFolder(id) {
160
+ const folders = listWorkflowFolders();
161
+ const childIds = collectChildFolderIds(folders, id);
162
+ const idsToDelete = new Set([id, ...childIds]);
163
+ const remaining = folders.filter(f => !idsToDelete.has(f.id));
164
+ writeJsonFile(workflowFoldersPath(), { folders: remaining });
165
+ // Delete workflows in removed folders
166
+ const workflows = readAllWorkflowFiles().filter(w => idsToDelete.has(w.folderId ?? ''));
167
+ for (const wf of workflows)
168
+ deleteWorkflow(wf.id);
169
+ }
170
+ // ---- Versions ----
171
+ const MAX_VERSIONS = 100;
172
+ export function listVersions(workflowId) {
173
+ const dir = versionsDir(workflowId);
174
+ if (!existsSync(dir))
175
+ return [];
176
+ return readdirSync(dir)
177
+ .filter(f => f.endsWith('.json'))
178
+ .map(f => {
179
+ try {
180
+ return JSON.parse(readFileSync(path.join(dir, f), 'utf-8'));
181
+ }
182
+ catch {
183
+ return null;
184
+ }
185
+ })
186
+ .filter(Boolean)
187
+ .sort((a, b) => b.createdAt - a.createdAt);
188
+ }
189
+ export function addVersion(workflowId, version) {
190
+ const dir = versionsDir(workflowId);
191
+ ensureDir(dir);
192
+ writeJsonFile(path.join(dir, `${version.id}.json`), version);
193
+ // Enforce limit
194
+ const versions = listVersions(workflowId);
195
+ if (versions.length > MAX_VERSIONS) {
196
+ versions.slice(MAX_VERSIONS).forEach((v) => {
197
+ const p = path.join(dir, `${v.id}.json`);
198
+ if (existsSync(p))
199
+ unlinkSync(p);
200
+ });
201
+ }
202
+ return version;
203
+ }
204
+ export function getVersion(workflowId, versionId) {
205
+ const file = path.join(versionsDir(workflowId), `${versionId}.json`);
206
+ return readJsonFile(file);
207
+ }
208
+ export function deleteVersion(workflowId, versionId) {
209
+ const file = path.join(versionsDir(workflowId), `${versionId}.json`);
210
+ if (existsSync(file))
211
+ unlinkSync(file);
212
+ }
213
+ export function clearVersions(workflowId) {
214
+ const dir = versionsDir(workflowId);
215
+ if (!existsSync(dir))
216
+ return;
217
+ readdirSync(dir).filter(f => f.endsWith('.json')).forEach(f => unlinkSync(path.join(dir, f)));
218
+ }
219
+ // ---- Execution Logs ----
220
+ const MAX_LOGS = 100;
221
+ export function listExecutionLogs(workflowId) {
222
+ const dir = executionHistoryDir(workflowId);
223
+ if (!existsSync(dir))
224
+ return [];
225
+ return readdirSync(dir)
226
+ .filter(f => f.endsWith('.json'))
227
+ .map(f => {
228
+ const p = path.join(dir, f);
229
+ try {
230
+ return { log: JSON.parse(readFileSync(p, 'utf-8')), mtime: statSync(p).mtimeMs };
231
+ }
232
+ catch {
233
+ return null;
234
+ }
235
+ })
236
+ .filter(Boolean)
237
+ .sort((a, b) => b.mtime - a.mtime)
238
+ .map((item) => item.log);
239
+ }
240
+ export function addExecutionLog(workflowId, log) {
241
+ const dir = executionHistoryDir(workflowId);
242
+ ensureDir(dir);
243
+ writeJsonFile(path.join(dir, `${log.id}.json`), log);
244
+ // Enforce limit
245
+ const files = readdirSync(dir).filter(f => f.endsWith('.json'));
246
+ if (files.length > MAX_LOGS) {
247
+ files.map(f => ({ f, mtime: statSync(path.join(dir, f)).mtimeMs }))
248
+ .sort((a, b) => b.mtime - a.mtime)
249
+ .slice(MAX_LOGS)
250
+ .forEach(item => { try {
251
+ unlinkSync(path.join(dir, item.f));
252
+ }
253
+ catch { /* ignore */ } });
254
+ }
255
+ return log;
256
+ }
257
+ export function listAllExecutionLogs(limit = 50) {
258
+ const dir = workflowsDir();
259
+ if (!existsSync(dir))
260
+ return [];
261
+ const results = [];
262
+ const wDirs = readdirSync(dir, { withFileTypes: true }).filter(d => d.isDirectory());
263
+ for (const wDir of wDirs) {
264
+ const wId = wDir.name;
265
+ const historyDir = executionHistoryDir(wId);
266
+ if (!existsSync(historyDir))
267
+ continue;
268
+ // Read workflow name for display
269
+ let workflowName = wId;
270
+ const wfPath = workflowPath(wId);
271
+ if (existsSync(wfPath)) {
272
+ try {
273
+ workflowName = JSON.parse(readFileSync(wfPath, 'utf-8')).name || wId;
274
+ }
275
+ catch { /* ignore */ }
276
+ }
277
+ readdirSync(historyDir)
278
+ .filter(f => f.endsWith('.json'))
279
+ .forEach(f => {
280
+ try {
281
+ const log = JSON.parse(readFileSync(path.join(historyDir, f), 'utf-8'));
282
+ results.push({ ...log, workflowName });
283
+ }
284
+ catch { /* ignore */ }
285
+ });
286
+ }
287
+ return results
288
+ .sort((a, b) => (b.startedAt || 0) - (a.startedAt || 0))
289
+ .slice(0, limit);
290
+ }
291
+ export function getExecutionLog(workflowId, logId) {
292
+ return readJsonFile(path.join(executionHistoryDir(workflowId), `${logId}.json`));
293
+ }
294
+ export function deleteExecutionLog(workflowId, logId) {
295
+ const file = path.join(executionHistoryDir(workflowId), `${logId}.json`);
296
+ if (existsSync(file))
297
+ unlinkSync(file);
298
+ }
299
+ export function clearExecutionLogs(workflowId) {
300
+ const dir = executionHistoryDir(workflowId);
301
+ if (!existsSync(dir))
302
+ return;
303
+ readdirSync(dir).filter(f => f.endsWith('.json')).forEach(f => unlinkSync(path.join(dir, f)));
304
+ }
305
+ // ---- Staging ----
306
+ export function loadStaging(workflowId) {
307
+ return readJsonFile(stagingPath(workflowId)) ?? [];
308
+ }
309
+ export function saveStaging(workflowId, nodes) {
310
+ ensureDir(workflowDir(workflowId));
311
+ writeJsonFile(stagingPath(workflowId), nodes);
312
+ }
313
+ export function clearStaging(workflowId) {
314
+ const file = stagingPath(workflowId);
315
+ if (existsSync(file))
316
+ unlinkSync(file);
317
+ }
318
+ // ---- Operation History ----
319
+ export function loadOperationHistory(workflowId) {
320
+ return readJsonFile(operationHistoryPath(workflowId)) ?? [];
321
+ }
322
+ export function saveOperationHistory(workflowId, entries) {
323
+ ensureDir(workflowDir(workflowId));
324
+ writeJsonFile(operationHistoryPath(workflowId), entries);
325
+ }
326
+ export function clearOperationHistory(workflowId) {
327
+ const file = operationHistoryPath(workflowId);
328
+ if (existsSync(file))
329
+ unlinkSync(file);
330
+ }
331
+ // ---- Workflow Agent Chat ----
332
+ export function loadChat(workflowId) {
333
+ return readJsonFile(chatPath(workflowId)) ?? [];
334
+ }
335
+ export function saveChat(workflowId, messages) {
336
+ ensureDir(workflowDir(workflowId));
337
+ writeJsonFile(chatPath(workflowId), messages);
338
+ }
339
+ export function clearChat(workflowId) {
340
+ const file = chatPath(workflowId);
341
+ if (existsSync(file))
342
+ unlinkSync(file);
343
+ }
344
+ // ---- Plugin Schemes ----
345
+ export function listPluginSchemes(workflowId, pluginId) {
346
+ const dir = path.join(pluginConfigsDir(workflowId), pluginId);
347
+ if (!existsSync(dir))
348
+ return [];
349
+ return readdirSync(dir).filter(f => f.endsWith('.json')).map(f => f.replace(/\.json$/, '')).sort();
350
+ }
351
+ export function readPluginScheme(workflowId, pluginId, schemeName) {
352
+ const file = pluginSchemePath(workflowId, pluginId, schemeName);
353
+ if (!existsSync(file))
354
+ throw new Error(`Plugin scheme ${schemeName} not found`);
355
+ return JSON.parse(readFileSync(file, 'utf-8'));
356
+ }
357
+ export function savePluginScheme(workflowId, pluginId, schemeName, data) {
358
+ const dir = path.join(pluginConfigsDir(workflowId), pluginId);
359
+ ensureDir(dir);
360
+ writeJsonFile(pluginSchemePath(workflowId, pluginId, schemeName), data);
361
+ }
362
+ export function deletePluginScheme(workflowId, pluginId, schemeName) {
363
+ const file = pluginSchemePath(workflowId, pluginId, schemeName);
364
+ if (existsSync(file))
365
+ unlinkSync(file);
366
+ }
367
+ // ---- Internal helpers ----
368
+ function readAllWorkflowFiles() {
369
+ const dir = workflowsDir();
370
+ if (!existsSync(dir))
371
+ return [];
372
+ const items = [];
373
+ const entries = readdirSync(dir, { withFileTypes: true });
374
+ for (const entry of entries) {
375
+ if (!entry.isDirectory())
376
+ continue;
377
+ const file = path.join(dir, entry.name, 'workflow.json');
378
+ if (!existsSync(file))
379
+ continue;
380
+ try {
381
+ items.push(JSON.parse(readFileSync(file, 'utf-8')));
382
+ }
383
+ catch { /* skip broken */ }
384
+ }
385
+ return items.sort((a, b) => b.updatedAt - a.updatedAt);
386
+ }
387
+ function readWorkflowFile(workflowId) {
388
+ const file = workflowPath(workflowId);
389
+ if (!existsSync(file))
390
+ return null;
391
+ try {
392
+ return JSON.parse(readFileSync(file, 'utf-8'));
393
+ }
394
+ catch {
395
+ return null;
396
+ }
397
+ }
398
+ function collectChildFolderIds(folders, parentId) {
399
+ const children = folders.filter(f => f.parentId === parentId);
400
+ return children.reduce((acc, child) => [...acc, child.id, ...collectChildFolderIds(folders, child.id)], []);
39
401
  }
402
+ // Need statSync for execution log sorting
403
+ import { statSync } from 'node:fs';
40
404
  //# sourceMappingURL=workflow-store.js.map
@@ -183,10 +183,12 @@ function formatRuntimeToolsLine(runtimeKind) {
183
183
  : runtimeKind === 'codex'
184
184
  ? 'Bash, file edits, WebSearch, todo list, MCP tools'
185
185
  : runtimeKind === 'langchain'
186
- ? 'Agent Spaces function tools'
186
+ ? 'Agent Spaces function tools and configured MCP tools'
187
187
  : runtimeKind === 'hermes'
188
188
  ? 'Hermes CLI tools and configured skills'
189
- : 'runtime-specific tools exposed by the selected adapter';
189
+ : runtimeKind === 'oh-my-pi'
190
+ ? 'Oh My Pi built-in tools, discovered extensions, MCP tools, skills, and Agent Spaces function tools'
191
+ : 'runtime-specific tools exposed by the selected adapter';
190
192
  return `- Runtime tools available through ${runtimeName}: ${tools}`;
191
193
  }
192
194
  function formatRuntimeName(runtimeKind) {
@@ -199,6 +201,8 @@ function formatRuntimeName(runtimeKind) {
199
201
  return 'LangChain';
200
202
  case 'hermes':
201
203
  return 'Hermes';
204
+ case 'oh-my-pi':
205
+ return 'Oh My Pi';
202
206
  case 'open-agent-sdk':
203
207
  return 'OpenAgent SDK';
204
208
  default: