@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.
- package/dist/adapters/agent-runtime.js +4 -0
- package/dist/adapters/codex-function-tool-bridge.js +9 -1
- package/dist/adapters/git.js +13 -2
- package/dist/adapters/hermes-runtime.js +711 -108
- package/dist/adapters/langchain-runtime.js +384 -21
- package/dist/adapters/oh-my-pi-runtime.js +858 -0
- package/dist/adapters/open-agent-sdk-runtime.js +202 -5
- package/dist/adapters/open-agent-sdk-runtime.test.js +35 -0
- package/dist/agents/agent-message-parts.js +52 -2
- package/dist/agents/issue-task-controller.js +4 -2
- package/dist/agents/title-generator-agent.js +120 -0
- package/dist/app.js +59 -6
- package/dist/routes/agent-sse.js +70 -7
- package/dist/routes/channel.js +23 -2
- package/dist/routes/chat-run.js +191 -0
- package/dist/routes/chat.js +142 -0
- package/dist/routes/command.js +16 -0
- package/dist/routes/data.js +189 -0
- package/dist/routes/git.js +10 -1
- package/dist/routes/import.js +199 -0
- package/dist/routes/issue.js +16 -4
- package/dist/routes/plugin.js +69 -0
- package/dist/routes/version.js +2 -1
- package/dist/routes/workflow-hook.js +71 -0
- package/dist/routes/workflow.js +282 -7
- package/dist/routes/workspace.js +13 -4
- package/dist/services/agent.js +123 -36
- package/dist/services/ai-text.js +185 -0
- package/dist/services/builtin-tools/index.js +1 -0
- package/dist/services/builtin-tools/workflow-editor-tools.js +509 -0
- package/dist/services/builtin-tools/workflow-exec-tools.js +320 -0
- package/dist/services/chat.js +134 -0
- package/dist/services/command-process-manager.js +16 -0
- package/dist/services/execution-manager.js +1346 -0
- package/dist/services/generated-title.js +59 -0
- package/dist/services/gitignore.js +22 -18
- package/dist/services/interaction-manager.js +114 -0
- package/dist/services/issue-retry.js +25 -0
- package/dist/services/output-style.js +8 -1
- package/dist/services/plugin.js +257 -0
- package/dist/services/prompt-template.js +8 -1
- package/dist/services/pty.js +20 -0
- package/dist/services/search.js +16 -6
- package/dist/services/skill.js +2 -0
- package/dist/services/version.js +17 -7
- package/dist/services/workflow-command-runner.js +5 -4
- package/dist/services/workflow-trigger-service.js +137 -0
- package/dist/services/workflow.js +199 -36
- package/dist/storage/agent-store.js +8 -0
- package/dist/storage/chat-store.js +151 -0
- package/dist/storage/database-store.js +6 -0
- package/dist/storage/json-store.js +2 -1
- package/dist/storage/kanban-store.js +6 -0
- package/dist/storage/workflow-store.js +386 -22
- package/dist/ws/agent-prompt.js +6 -2
- package/dist/ws/agent-runner.js +29 -18
- package/dist/ws/chat-handler.js +89 -0
- package/dist/ws/connection-manager.js +49 -2
- package/dist/ws/execution-channels.js +88 -0
- package/dist/ws/handler.js +32 -5
- package/dist/ws/message-parts.js +130 -12
- package/dist/ws/terminal-handler.js +26 -0
- package/package.json +12 -1
- 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
|
-
|
|
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
|
-
//
|
|
3
|
-
|
|
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
|
-
|
|
7
|
+
// ---- Path helpers ----
|
|
8
|
+
export function workflowsDir() {
|
|
6
9
|
return path.join(getDataDir(), 'workflows');
|
|
7
10
|
}
|
|
8
|
-
function
|
|
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
|
|
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
|
-
|
|
15
|
-
|
|
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
|
-
|
|
120
|
+
migrateFromLegacyFormat();
|
|
121
|
+
return readWorkflowFile(workflowId);
|
|
19
122
|
}
|
|
20
123
|
export function createWorkflow(workflow) {
|
|
21
|
-
ensureDir(
|
|
22
|
-
writeJsonFile(
|
|
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
|
-
|
|
29
|
-
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
package/dist/ws/agent-prompt.js
CHANGED
|
@@ -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
|
-
:
|
|
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:
|