@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
@@ -168,6 +168,8 @@ export function importSkillFromStore(storePath, group) {
168
168
  const candidates = [
169
169
  join(process.cwd(), 'public', 'skills', storePath),
170
170
  join(import.meta.dirname ?? '', '..', 'public', 'skills', storePath),
171
+ join(process.cwd(), 'packages', 'agents', 'skills', storePath),
172
+ join(import.meta.dirname ?? '', '..', '..', '..', 'agents', 'skills', storePath),
171
173
  ];
172
174
  for (const c of candidates) {
173
175
  if (existsSync(c) && statSync(c).isDirectory()) {
@@ -1,4 +1,4 @@
1
- import { readFileSync } from 'node:fs';
1
+ import { existsSync, readFileSync } from 'node:fs';
2
2
  import { join, dirname } from 'node:path';
3
3
  import { fileURLToPath } from 'node:url';
4
4
  const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -8,13 +8,23 @@ const CACHE_TTL = 3600_000; // 1 hour
8
8
  export function getLocalVersion() {
9
9
  if (localVersion !== '')
10
10
  return localVersion;
11
- try {
12
- const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'));
13
- localVersion = pkg.version || '0.0.0';
14
- }
15
- catch {
16
- localVersion = '0.0.0';
11
+ const packageJsonPaths = [
12
+ join(__dirname, '..', '..', 'package.json'),
13
+ join(__dirname, '..', 'package.json'),
14
+ ];
15
+ for (const packageJsonPath of packageJsonPaths) {
16
+ if (!existsSync(packageJsonPath))
17
+ continue;
18
+ try {
19
+ const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
20
+ localVersion = pkg.version || '0.0.0';
21
+ return localVersion;
22
+ }
23
+ catch {
24
+ // Try the next package.json candidate before falling back.
25
+ }
17
26
  }
27
+ localVersion = '0.0.0';
18
28
  return localVersion;
19
29
  }
20
30
  export function getCachedLatest() {
@@ -2,12 +2,13 @@ import { exec } from 'child_process';
2
2
  import { getWorkspace } from '../storage/workspace-store.js';
3
3
  export async function executeCommandNode(workspaceId, node) {
4
4
  const workspace = getWorkspace(workspaceId);
5
- const cwd = node.data.cwd || workspace?.boundDirs?.[0] || process.cwd();
5
+ const data = node.data;
6
+ const cwd = data.cwd || workspace?.boundDirs?.[0] || process.cwd();
6
7
  return new Promise((resolve) => {
7
- exec(node.data.script, {
8
+ exec(data.script, {
8
9
  cwd,
9
- env: { ...process.env, ...node.data.env },
10
- shell: node.data.shell || undefined,
10
+ env: { ...process.env, ...data.env },
11
+ shell: data.shell || undefined,
11
12
  timeout: 300_000,
12
13
  maxBuffer: 10 * 1024 * 1024,
13
14
  }, (error, stdout, stderr) => {
@@ -0,0 +1,137 @@
1
+ import * as store from '../storage/workflow-store.js';
2
+ export class WorkflowTriggerService {
3
+ port;
4
+ cronJobs = new Map();
5
+ hookIndex = new Map();
6
+ nodeCron = null;
7
+ executionManager = null;
8
+ constructor(port = 3100) {
9
+ this.port = port;
10
+ try {
11
+ this.nodeCron = require('node-cron');
12
+ }
13
+ catch {
14
+ // node-cron not available
15
+ }
16
+ }
17
+ setExecutionManager(em) {
18
+ this.executionManager = em;
19
+ }
20
+ async start() {
21
+ const workflows = store.listWorkflows();
22
+ for (const wf of workflows) {
23
+ this.registerTriggers(wf);
24
+ }
25
+ console.log(`[TriggerService] Started. ${this.cronJobs.size} cron jobs, ${this.hookIndex.size} hooks registered`);
26
+ }
27
+ reloadWorkflow(workflowId) {
28
+ this.clearTriggersForWorkflow(workflowId);
29
+ const wf = store.getWorkflow(workflowId);
30
+ if (wf)
31
+ this.registerTriggers(wf);
32
+ }
33
+ removeWorkflow(workflowId) {
34
+ this.clearTriggersForWorkflow(workflowId);
35
+ }
36
+ getHookBindings(hookName) {
37
+ return Array.from(this.hookIndex.get(hookName) ?? []);
38
+ }
39
+ getHookConflicts(hookName, excludeWorkflowId) {
40
+ const bindings = this.hookIndex.get(hookName) ?? new Set();
41
+ const ids = Array.from(bindings)
42
+ .map(b => b.workflowId)
43
+ .filter(id => id !== excludeWorkflowId);
44
+ return { conflictWorkflowIds: [...new Set(ids)] };
45
+ }
46
+ getHookUrl(hookName) {
47
+ return `http://localhost:${this.port}/api/workflows/hook/${hookName}`;
48
+ }
49
+ validateCron(cronExpr) {
50
+ if (!this.nodeCron)
51
+ return { valid: false, nextRuns: [], error: 'node-cron not available' };
52
+ if (!this.nodeCron.validate(cronExpr)) {
53
+ return { valid: false, nextRuns: [], error: 'Invalid cron expression' };
54
+ }
55
+ try {
56
+ const CronExpressionParser = require('cron-parser');
57
+ const interval = CronExpressionParser.parse(cronExpr);
58
+ const nextRuns = [];
59
+ for (let i = 0; i < 5; i++) {
60
+ const iso = interval.next().toISOString();
61
+ if (iso)
62
+ nextRuns.push(iso);
63
+ }
64
+ return { valid: true, nextRuns };
65
+ }
66
+ catch (err) {
67
+ return { valid: false, nextRuns: [], error: err.message };
68
+ }
69
+ }
70
+ stop() {
71
+ for (const [, task] of this.cronJobs) {
72
+ task.stop();
73
+ }
74
+ this.cronJobs.clear();
75
+ this.hookIndex.clear();
76
+ }
77
+ registerTriggers(wf) {
78
+ if (!wf.triggers)
79
+ return;
80
+ for (const trigger of wf.triggers) {
81
+ if (!trigger.enabled)
82
+ continue;
83
+ if (trigger.type === 'cron') {
84
+ this.registerCronJob(wf.id, trigger);
85
+ }
86
+ else if (trigger.type === 'hook') {
87
+ this.registerHookBinding(wf.id, trigger);
88
+ }
89
+ }
90
+ }
91
+ registerCronJob(workflowId, trigger) {
92
+ if (!this.nodeCron)
93
+ return;
94
+ const key = `${workflowId}:${trigger.id}`;
95
+ try {
96
+ const task = this.nodeCron.schedule(trigger.cron, () => {
97
+ console.log(`[TriggerService] Cron fired for workflow ${workflowId}`);
98
+ if (this.executionManager) {
99
+ this.executionManager.execute({ workflowId }, '__cron__').catch((err) => {
100
+ console.error(`[TriggerService] Cron execution failed for ${workflowId}: ${err.message}`);
101
+ });
102
+ }
103
+ }, { timezone: trigger.timezone });
104
+ this.cronJobs.set(key, task);
105
+ }
106
+ catch (err) {
107
+ console.error(`[TriggerService] Invalid cron "${trigger.cron}" for workflow ${workflowId}: ${err.message}`);
108
+ }
109
+ }
110
+ registerHookBinding(workflowId, trigger) {
111
+ let bindings = this.hookIndex.get(trigger.hookName);
112
+ if (!bindings) {
113
+ bindings = new Set();
114
+ this.hookIndex.set(trigger.hookName, bindings);
115
+ }
116
+ bindings.add({ workflowId, triggerId: trigger.id });
117
+ }
118
+ clearTriggersForWorkflow(workflowId) {
119
+ for (const [key, task] of this.cronJobs) {
120
+ if (key.startsWith(`${workflowId}:`)) {
121
+ task.stop();
122
+ this.cronJobs.delete(key);
123
+ }
124
+ }
125
+ for (const [hookName, bindings] of this.hookIndex) {
126
+ for (const binding of bindings) {
127
+ if (binding.workflowId === workflowId) {
128
+ bindings.delete(binding);
129
+ }
130
+ }
131
+ if (bindings.size === 0) {
132
+ this.hookIndex.delete(hookName);
133
+ }
134
+ }
135
+ }
136
+ }
137
+ //# sourceMappingURL=workflow-trigger-service.js.map
@@ -1,11 +1,16 @@
1
1
  // packages/server/src/services/workflow.ts
2
- // NOTE: All storage functions are synchronous (matching json-store pattern).
3
- // Service functions are also synchronous where they only call sync storage.
4
- // Route handlers wrap in async for Express compatibility.
5
2
  import { v4 as uuid } from 'uuid';
6
- import * as workflowStore from '../storage/workflow-store.js';
3
+ import * as store from '../storage/workflow-store.js';
7
4
  import { listTemplates } from './agent.js';
8
- // --- DAG Validation ---
5
+ function getAgentData(node) {
6
+ if (node.type !== 'agent')
7
+ return null;
8
+ return node.data;
9
+ }
10
+ function getLabel(node) {
11
+ return node.data.label || node.label;
12
+ }
13
+ // ---- DAG Validation ----
9
14
  function topologicalSort(nodes, edges) {
10
15
  const adj = new Map();
11
16
  const inDegree = new Map();
@@ -14,8 +19,10 @@ function topologicalSort(nodes, edges) {
14
19
  inDegree.set(node.id, 0);
15
20
  }
16
21
  for (const edge of edges) {
17
- adj.get(edge.source).push(edge.target);
18
- inDegree.set(edge.target, (inDegree.get(edge.target) ?? 0) + 1);
22
+ const source = edge.source;
23
+ const target = edge.target;
24
+ adj.get(source).push(target);
25
+ inDegree.set(target, (inDegree.get(target) ?? 0) + 1);
19
26
  }
20
27
  const queue = [];
21
28
  for (const [id, deg] of inDegree) {
@@ -67,14 +74,17 @@ export function validateDAG(template) {
67
74
  }
68
75
  return null;
69
76
  }
70
- // --- Role Staleness Resolution ---
77
+ // ---- Role Staleness Resolution ----
71
78
  function resolveStaleRoles(nodes) {
72
79
  const agentMap = new Map(listTemplates().map(a => [a.id, a]));
73
80
  const invalidIds = [];
74
81
  const resolved = nodes.map(node => {
75
82
  if (node.type === 'command')
76
83
  return node;
77
- const agent = agentMap.get(node.data.agentConfigId);
84
+ const agentData = getAgentData(node);
85
+ if (!agentData)
86
+ return node;
87
+ const agent = agentMap.get(agentData.agentConfigId);
78
88
  if (!agent) {
79
89
  invalidIds.push(node.id);
80
90
  return node;
@@ -91,39 +101,43 @@ function resolveStaleRoles(nodes) {
91
101
  });
92
102
  return { nodes: resolved, invalidIds };
93
103
  }
94
- // --- CRUD (synchronous) ---
95
- export function listWorkflows() {
96
- return workflowStore.listWorkflows();
104
+ // ---- Workflow CRUD ----
105
+ export function listWorkflows(folderId) {
106
+ return store.listWorkflows(folderId);
97
107
  }
98
108
  export function getWorkflow(workflowId) {
99
- return workflowStore.getWorkflow(workflowId);
109
+ return store.getWorkflow(workflowId);
100
110
  }
101
111
  export function createWorkflow(input) {
102
- const now = new Date().toISOString();
112
+ const now = Date.now();
103
113
  const nodes = input.nodes ?? [];
104
114
  const edges = input.edges ?? [];
105
115
  const { nodes: resolvedNodes, invalidIds } = resolveStaleRoles(nodes);
106
116
  if (invalidIds.length > 0) {
107
117
  throw new Error(`Invalid agent references in nodes: ${invalidIds.join(', ')}`);
108
118
  }
109
- const template = {
119
+ const workflow = {
110
120
  id: uuid(),
111
121
  name: input.name,
122
+ folderId: input.folderId ?? null,
123
+ icon: input.icon,
112
124
  description: input.description,
125
+ tags: input.tags,
113
126
  nodes: resolvedNodes,
114
127
  edges,
115
- viewport: input.viewport,
116
128
  createdAt: now,
117
129
  updatedAt: now,
130
+ triggers: input.triggers,
131
+ groups: input.groups,
118
132
  };
119
- const error = validateDAG(template);
133
+ const error = validateDAG(workflow);
120
134
  if (error)
121
135
  throw new Error(error);
122
- workflowStore.createWorkflow(template);
123
- return template;
136
+ store.createWorkflow(workflow);
137
+ return workflow;
124
138
  }
125
139
  export function updateWorkflow(workflowId, updates) {
126
- const existing = workflowStore.getWorkflow(workflowId);
140
+ const existing = store.getWorkflow(workflowId);
127
141
  if (!existing)
128
142
  throw new Error('Workflow not found');
129
143
  let nodes = updates.nodes ?? existing.nodes;
@@ -140,27 +154,27 @@ export function updateWorkflow(workflowId, updates) {
140
154
  nodes,
141
155
  id: existing.id,
142
156
  createdAt: existing.createdAt,
143
- updatedAt: new Date().toISOString(),
157
+ updatedAt: Date.now(),
144
158
  };
145
159
  if (updates.nodes || updates.edges) {
146
160
  const error = validateDAG(updated);
147
161
  if (error)
148
162
  throw new Error(error);
149
163
  }
150
- workflowStore.updateWorkflow(updated);
164
+ store.updateWorkflow(updated);
151
165
  return updated;
152
166
  }
153
167
  export function deleteWorkflow(workflowId) {
154
- const existing = workflowStore.getWorkflow(workflowId);
168
+ const existing = store.getWorkflow(workflowId);
155
169
  if (!existing)
156
170
  throw new Error('Workflow not found');
157
- workflowStore.deleteWorkflow(workflowId);
171
+ store.deleteWorkflow(workflowId);
158
172
  }
159
173
  export function duplicateWorkflow(workflowId) {
160
- const existing = workflowStore.getWorkflow(workflowId);
174
+ const existing = store.getWorkflow(workflowId);
161
175
  if (!existing)
162
176
  throw new Error('Workflow not found');
163
- const now = new Date().toISOString();
177
+ const now = Date.now();
164
178
  const duplicated = {
165
179
  ...existing,
166
180
  id: uuid(),
@@ -168,9 +182,124 @@ export function duplicateWorkflow(workflowId) {
168
182
  createdAt: now,
169
183
  updatedAt: now,
170
184
  };
171
- workflowStore.createWorkflow(duplicated);
185
+ store.createWorkflow(duplicated);
172
186
  return duplicated;
173
187
  }
188
+ // ---- Folder CRUD ----
189
+ export function listFolders() {
190
+ return store.listWorkflowFolders();
191
+ }
192
+ export function createFolder(input) {
193
+ const folders = store.listWorkflowFolders();
194
+ const siblings = folders.filter(f => f.parentId === (input.parentId ?? null));
195
+ const folder = {
196
+ id: uuid(),
197
+ name: input.name,
198
+ parentId: input.parentId ?? null,
199
+ order: siblings.length > 0 ? Math.max(...siblings.map(f => f.order)) + 1 : 0,
200
+ createdAt: Date.now(),
201
+ };
202
+ store.createWorkflowFolder(folder);
203
+ return folder;
204
+ }
205
+ export function updateFolder(id, updates) {
206
+ store.updateWorkflowFolder(id, updates);
207
+ }
208
+ export function deleteFolder(id) {
209
+ store.deleteWorkflowFolder(id);
210
+ }
211
+ // ---- Version CRUD ----
212
+ export function listVersions(workflowId) {
213
+ return store.listVersions(workflowId);
214
+ }
215
+ export function createVersion(workflowId, name) {
216
+ const workflow = store.getWorkflow(workflowId);
217
+ if (!workflow)
218
+ throw new Error('Workflow not found');
219
+ const version = {
220
+ id: uuid(),
221
+ workflowId,
222
+ name: name || `v${store.listVersions(workflowId).length + 1}`,
223
+ snapshot: {
224
+ nodes: JSON.parse(JSON.stringify(workflow.nodes)),
225
+ edges: JSON.parse(JSON.stringify(workflow.edges)),
226
+ },
227
+ createdAt: Date.now(),
228
+ };
229
+ store.addVersion(workflowId, version);
230
+ return version;
231
+ }
232
+ export function getVersion(workflowId, versionId) {
233
+ return store.getVersion(workflowId, versionId);
234
+ }
235
+ export function deleteVersion(workflowId, versionId) {
236
+ store.deleteVersion(workflowId, versionId);
237
+ }
238
+ export function clearVersions(workflowId) {
239
+ store.clearVersions(workflowId);
240
+ }
241
+ // ---- Execution Log CRUD ----
242
+ export function listExecutionLogs(workflowId) {
243
+ return store.listExecutionLogs(workflowId);
244
+ }
245
+ export function listAllExecutionLogs(limit) {
246
+ return store.listAllExecutionLogs(limit);
247
+ }
248
+ export function getExecutionLog(workflowId, logId) {
249
+ return store.getExecutionLog(workflowId, logId);
250
+ }
251
+ export function deleteExecutionLog(workflowId, logId) {
252
+ store.deleteExecutionLog(workflowId, logId);
253
+ }
254
+ export function clearExecutionLogs(workflowId) {
255
+ store.clearExecutionLogs(workflowId);
256
+ }
257
+ // ---- Staging ----
258
+ export function loadStaging(workflowId) {
259
+ return store.loadStaging(workflowId);
260
+ }
261
+ export function saveStaging(workflowId, nodes) {
262
+ store.saveStaging(workflowId, nodes);
263
+ }
264
+ export function clearStaging(workflowId) {
265
+ store.clearStaging(workflowId);
266
+ }
267
+ // ---- Operation History ----
268
+ export function loadOperationHistory(workflowId) {
269
+ return store.loadOperationHistory(workflowId);
270
+ }
271
+ export function saveOperationHistory(workflowId, entries) {
272
+ store.saveOperationHistory(workflowId, entries);
273
+ }
274
+ export function clearOperationHistory(workflowId) {
275
+ store.clearOperationHistory(workflowId);
276
+ }
277
+ // ---- Workflow Agent Chat ----
278
+ export function loadChat(workflowId) {
279
+ return store.loadChat(workflowId);
280
+ }
281
+ export function saveChat(workflowId, messages) {
282
+ store.saveChat(workflowId, messages);
283
+ }
284
+ export function clearChat(workflowId) {
285
+ store.clearChat(workflowId);
286
+ }
287
+ // ---- Plugin Config Schemes ----
288
+ export function listPluginSchemes(workflowId, pluginId) {
289
+ return store.listPluginSchemes(workflowId, pluginId);
290
+ }
291
+ export function readPluginScheme(workflowId, pluginId, schemeName) {
292
+ return store.readPluginScheme(workflowId, pluginId, schemeName);
293
+ }
294
+ export function createPluginScheme(workflowId, pluginId, schemeName) {
295
+ store.savePluginScheme(workflowId, pluginId, schemeName, {});
296
+ }
297
+ export function savePluginScheme(workflowId, pluginId, schemeName, data) {
298
+ store.savePluginScheme(workflowId, pluginId, schemeName, data);
299
+ }
300
+ export function deletePluginScheme(workflowId, pluginId, schemeName) {
301
+ store.deletePluginScheme(workflowId, pluginId, schemeName);
302
+ }
174
303
  export function mapWorkflowToTaskDrafts(template) {
175
304
  const dependsOn = new Map();
176
305
  for (const node of template.nodes) {
@@ -180,41 +309,75 @@ export function mapWorkflowToTaskDrafts(template) {
180
309
  dependsOn.get(edge.target)?.push(edge.source);
181
310
  }
182
311
  return template.nodes.map(node => {
312
+ const label = getLabel(node);
183
313
  if (node.type === 'command') {
184
314
  return {
185
315
  key: node.id,
186
- title: node.data.label,
187
- description: `Run command: ${node.data.label}`,
316
+ title: label,
317
+ description: `Run command: ${label}`,
188
318
  agentConfigId: undefined,
189
319
  dependsOnKeys: dependsOn.get(node.id)?.length ? dependsOn.get(node.id) : undefined,
190
320
  sandboxDirs: undefined,
191
321
  commandNode: node,
192
322
  };
193
323
  }
324
+ const agentData = getAgentData(node);
325
+ const taskTitle = (agentData?.taskTitleTemplate || label);
326
+ const taskDesc = agentData?.taskDescriptionTemplate || `Task assigned to ${label} (${agentData?.role || 'unknown'})`;
194
327
  return {
195
328
  key: node.id,
196
- title: node.data.taskTitleTemplate || node.data.label,
197
- description: node.data.taskDescriptionTemplate || `Task assigned to ${node.data.label} (${node.data.role})`,
198
- agentConfigId: node.data.agentConfigId,
329
+ title: taskTitle,
330
+ description: taskDesc,
331
+ agentConfigId: agentData?.agentConfigId,
199
332
  dependsOnKeys: dependsOn.get(node.id)?.length ? dependsOn.get(node.id) : undefined,
200
333
  sandboxDirs: undefined,
201
334
  };
202
335
  });
203
336
  }
204
- // --- Run-time Validation ---
337
+ // ---- Run-time Validation ----
205
338
  export function validateWorkflowForRun(_workspaceId, template, memberAgentIds) {
206
339
  const agentMap = new Map(listTemplates().map((a) => [a.id, a]));
207
340
  for (const node of template.nodes) {
208
341
  if (node.type === 'command')
209
342
  continue;
210
- const agent = agentMap.get(node.data.agentConfigId);
343
+ const agentData = getAgentData(node);
344
+ if (!agentData)
345
+ continue;
346
+ const label = getLabel(node);
347
+ const agent = agentMap.get(agentData.agentConfigId);
211
348
  if (!agent)
212
- return `Agent "${node.data.label}" (${node.data.agentConfigId}) no longer exists`;
349
+ return `Agent "${label}" (${agentData.agentConfigId}) no longer exists`;
213
350
  if (!agent.enabled)
214
351
  return `Agent "${agent.name}" is disabled`;
215
- if (!memberAgentIds.has(node.data.agentConfigId))
352
+ if (!memberAgentIds.has(agentData.agentConfigId))
216
353
  return `Agent "${agent.name}" is not in the issue channel members`;
217
354
  }
218
355
  return null;
219
356
  }
357
+ // ---- Cron Validation ----
358
+ export function validateCron(cronExpr) {
359
+ try {
360
+ const { validate } = require('node-cron');
361
+ if (!validate(cronExpr)) {
362
+ return { valid: false, nextRuns: [], error: 'Invalid cron expression' };
363
+ }
364
+ }
365
+ catch {
366
+ // If node-cron validate fails to load, continue with cron-parser
367
+ }
368
+ try {
369
+ const CronExpressionParser = require('cron-parser');
370
+ const interval = CronExpressionParser.parse(cronExpr);
371
+ const nextRuns = [];
372
+ for (let i = 0; i < 5; i++) {
373
+ const iso = interval.next().toISOString();
374
+ if (iso)
375
+ nextRuns.push(iso);
376
+ }
377
+ return { valid: true, nextRuns };
378
+ }
379
+ catch (err) {
380
+ return { valid: false, nextRuns: [], error: err.message };
381
+ }
382
+ }
220
383
  //# sourceMappingURL=workflow.js.map
@@ -149,6 +149,7 @@ export function getAgentUsageDashboard(days = 30) {
149
149
  dailyMap.set(key, {
150
150
  date: key,
151
151
  label: date.toLocaleDateString('en-US', { weekday: 'short' }),
152
+ requests: 0,
152
153
  inputTokens: 0,
153
154
  outputTokens: 0,
154
155
  totalTokens: 0,
@@ -161,6 +162,7 @@ export function getAgentUsageDashboard(days = 30) {
161
162
  for (const record of records) {
162
163
  const day = dailyMap.get(record.completedAt.slice(0, 10));
163
164
  if (day) {
165
+ day.requests += 1;
164
166
  day.inputTokens += record.inputTokens + record.cachedInputTokens;
165
167
  day.outputTokens += record.outputTokens + record.reasoningTokens;
166
168
  day.totalTokens += record.totalTokens;
@@ -323,4 +325,10 @@ function findConfiguredModelCost(model) {
323
325
  outputPerMillion: toMoney(configured.cost.outputPerMillion),
324
326
  };
325
327
  }
328
+ export function closeDb() {
329
+ if (db) {
330
+ db.close();
331
+ db = null;
332
+ }
333
+ }
326
334
  //# sourceMappingURL=agent-store.js.map