groove-dev 0.27.142 → 0.27.144
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/node_modules/@groove-dev/cli/package.json +1 -1
- package/node_modules/@groove-dev/daemon/package.json +1 -1
- package/node_modules/@groove-dev/daemon/src/api.js +1086 -6532
- package/node_modules/@groove-dev/daemon/src/gateways/manager.js +35 -1
- package/node_modules/@groove-dev/daemon/src/index.js +3 -0
- package/node_modules/@groove-dev/daemon/src/journalist.js +23 -13
- package/node_modules/@groove-dev/daemon/src/mlx-server.js +365 -0
- package/node_modules/@groove-dev/daemon/src/model-lab.js +308 -12
- package/node_modules/@groove-dev/daemon/src/pm.js +1 -1
- package/node_modules/@groove-dev/daemon/src/process.js +2 -2
- package/node_modules/@groove-dev/daemon/src/providers/local.js +36 -8
- package/node_modules/@groove-dev/daemon/src/registry.js +21 -5
- package/node_modules/@groove-dev/daemon/src/routes/agents.js +889 -0
- package/node_modules/@groove-dev/daemon/src/routes/coordination.js +318 -0
- package/node_modules/@groove-dev/daemon/src/routes/files.js +751 -0
- package/node_modules/@groove-dev/daemon/src/routes/integrations.js +485 -0
- package/node_modules/@groove-dev/daemon/src/routes/network.js +1784 -0
- package/node_modules/@groove-dev/daemon/src/routes/providers.js +755 -0
- package/node_modules/@groove-dev/daemon/src/routes/schedules.js +110 -0
- package/node_modules/@groove-dev/daemon/src/routes/teams.js +650 -0
- package/node_modules/@groove-dev/daemon/src/scheduler.js +456 -24
- package/node_modules/@groove-dev/daemon/src/teams.js +1 -1
- package/node_modules/@groove-dev/daemon/src/validate.js +38 -1
- package/node_modules/@groove-dev/daemon/templates/mlx-setup.json +12 -0
- package/node_modules/@groove-dev/daemon/templates/tgi-setup.json +1 -1
- package/node_modules/@groove-dev/daemon/templates/vllm-setup.json +1 -1
- package/node_modules/@groove-dev/daemon/test/introducer.test.js +3 -3
- package/node_modules/@groove-dev/daemon/test/journalist.test.js +7 -10
- package/node_modules/@groove-dev/daemon/test/registry.test.js +38 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-BcoF6_eF.js +1012 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-Dd7qhiEd.css +1 -0
- package/node_modules/@groove-dev/gui/dist/index.html +2 -2
- package/node_modules/@groove-dev/gui/package.json +1 -1
- package/{packages/gui/src/app.jsx → node_modules/@groove-dev/gui/src/App.jsx} +0 -2
- package/node_modules/@groove-dev/gui/src/app.css +35 -0
- package/node_modules/@groove-dev/gui/src/components/agents/agent-config.jsx +1 -128
- package/node_modules/@groove-dev/gui/src/components/agents/agent-feed.jsx +144 -31
- package/node_modules/@groove-dev/gui/src/components/agents/agent-node.jsx +8 -13
- package/node_modules/@groove-dev/gui/src/components/agents/code-review.jsx +159 -122
- package/node_modules/@groove-dev/gui/src/components/agents/diff-viewer.jsx +23 -23
- package/node_modules/@groove-dev/gui/src/components/agents/journalist-panel.jsx +1 -1
- package/node_modules/@groove-dev/gui/src/components/agents/spawn-wizard.jsx +2 -135
- package/node_modules/@groove-dev/gui/src/components/automations/automation-card.jsx +274 -0
- package/node_modules/@groove-dev/gui/src/components/automations/automation-wizard.jsx +1136 -0
- package/node_modules/@groove-dev/gui/src/components/dashboard/activity-feed.jsx +3 -3
- package/node_modules/@groove-dev/gui/src/components/dashboard/cache-ring.jsx +5 -5
- package/node_modules/@groove-dev/gui/src/components/dashboard/context-gauges.jsx +6 -8
- package/node_modules/@groove-dev/gui/src/components/dashboard/fleet-panel.jsx +8 -14
- package/node_modules/@groove-dev/gui/src/components/dashboard/intel-panel.jsx +238 -656
- package/node_modules/@groove-dev/gui/src/components/dashboard/kpi-card.jsx +3 -3
- package/node_modules/@groove-dev/gui/src/components/dashboard/routing-chart.jsx +3 -3
- package/node_modules/@groove-dev/gui/src/components/dashboard/team-burn-panel.jsx +1 -1
- package/node_modules/@groove-dev/gui/src/components/dashboard/token-chart.jsx +4 -4
- package/node_modules/@groove-dev/gui/src/components/editor/selection-menu.jsx +2 -0
- package/node_modules/@groove-dev/gui/src/components/lab/lab-assistant.jsx +316 -82
- package/node_modules/@groove-dev/gui/src/components/lab/metrics-panel.jsx +187 -32
- package/node_modules/@groove-dev/gui/src/components/lab/parameter-panel.jsx +195 -14
- package/node_modules/@groove-dev/gui/src/components/lab/runtime-config.jsx +286 -102
- package/node_modules/@groove-dev/gui/src/components/layout/activity-bar.jsx +2 -4
- package/node_modules/@groove-dev/gui/src/components/layout/terminal-panel.jsx +4 -2
- package/node_modules/@groove-dev/gui/src/components/layout/welcome-splash.jsx +137 -108
- package/node_modules/@groove-dev/gui/src/components/network/network-health.jsx +2 -2
- package/node_modules/@groove-dev/gui/src/components/network/performance-dashboard.jsx +4 -4
- package/node_modules/@groove-dev/gui/src/components/settings/ssh-wizard.jsx +81 -99
- package/node_modules/@groove-dev/gui/src/components/ui/sheet.jsx +5 -2
- package/node_modules/@groove-dev/gui/src/lib/cron.js +64 -0
- package/node_modules/@groove-dev/gui/src/lib/status.js +24 -24
- package/node_modules/@groove-dev/gui/src/lib/theme-hex.js +1 -0
- package/node_modules/@groove-dev/gui/src/stores/groove.js +34 -3144
- package/node_modules/@groove-dev/gui/src/stores/helpers.js +10 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/agents-slice.js +452 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/automations-slice.js +96 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/chat-slice.js +227 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/editor-slice.js +285 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/marketplace-slice.js +461 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/network-slice.js +361 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/preview-slice.js +109 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/providers-slice.js +897 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/teams-slice.js +413 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/ui-slice.js +98 -0
- package/node_modules/@groove-dev/gui/src/views/agents.jsx +5 -5
- package/node_modules/@groove-dev/gui/src/views/dashboard.jsx +12 -13
- package/node_modules/@groove-dev/gui/src/views/marketplace.jsx +191 -3
- package/node_modules/@groove-dev/gui/src/views/model-lab.jsx +17 -6
- package/node_modules/@groove-dev/gui/src/views/models.jsx +410 -509
- package/node_modules/@groove-dev/gui/src/views/network.jsx +3 -3
- package/node_modules/@groove-dev/gui/src/views/settings.jsx +81 -94
- package/node_modules/@groove-dev/gui/src/views/teams.jsx +40 -483
- package/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/daemon/package.json +1 -1
- package/packages/daemon/src/api.js +1086 -6532
- package/packages/daemon/src/gateways/manager.js +35 -1
- package/packages/daemon/src/index.js +3 -0
- package/packages/daemon/src/journalist.js +23 -13
- package/packages/daemon/src/mlx-server.js +365 -0
- package/packages/daemon/src/model-lab.js +308 -12
- package/packages/daemon/src/pm.js +1 -1
- package/packages/daemon/src/process.js +2 -2
- package/packages/daemon/src/providers/local.js +36 -8
- package/packages/daemon/src/registry.js +21 -5
- package/packages/daemon/src/routes/agents.js +889 -0
- package/packages/daemon/src/routes/coordination.js +318 -0
- package/packages/daemon/src/routes/files.js +751 -0
- package/packages/daemon/src/routes/integrations.js +485 -0
- package/packages/daemon/src/routes/network.js +1784 -0
- package/packages/daemon/src/routes/providers.js +755 -0
- package/packages/daemon/src/routes/schedules.js +110 -0
- package/packages/daemon/src/routes/teams.js +650 -0
- package/packages/daemon/src/scheduler.js +456 -24
- package/packages/daemon/src/teams.js +1 -1
- package/packages/daemon/src/validate.js +38 -1
- package/packages/daemon/templates/mlx-setup.json +12 -0
- package/packages/daemon/templates/tgi-setup.json +1 -1
- package/packages/daemon/templates/vllm-setup.json +1 -1
- package/packages/gui/dist/assets/index-BcoF6_eF.js +1012 -0
- package/packages/gui/dist/assets/index-Dd7qhiEd.css +1 -0
- package/packages/gui/dist/index.html +2 -2
- package/packages/gui/package.json +1 -1
- package/{node_modules/@groove-dev/gui/src/app.jsx → packages/gui/src/App.jsx} +0 -2
- package/packages/gui/src/app.css +35 -0
- package/packages/gui/src/components/agents/agent-config.jsx +1 -128
- package/packages/gui/src/components/agents/agent-feed.jsx +144 -31
- package/packages/gui/src/components/agents/agent-node.jsx +8 -13
- package/packages/gui/src/components/agents/code-review.jsx +159 -122
- package/packages/gui/src/components/agents/diff-viewer.jsx +23 -23
- package/packages/gui/src/components/agents/journalist-panel.jsx +1 -1
- package/packages/gui/src/components/agents/spawn-wizard.jsx +2 -135
- package/packages/gui/src/components/automations/automation-card.jsx +274 -0
- package/packages/gui/src/components/automations/automation-wizard.jsx +1136 -0
- package/packages/gui/src/components/dashboard/activity-feed.jsx +3 -3
- package/packages/gui/src/components/dashboard/cache-ring.jsx +5 -5
- package/packages/gui/src/components/dashboard/context-gauges.jsx +6 -8
- package/packages/gui/src/components/dashboard/fleet-panel.jsx +8 -14
- package/packages/gui/src/components/dashboard/intel-panel.jsx +238 -656
- package/packages/gui/src/components/dashboard/kpi-card.jsx +3 -3
- package/packages/gui/src/components/dashboard/routing-chart.jsx +3 -3
- package/packages/gui/src/components/dashboard/team-burn-panel.jsx +1 -1
- package/packages/gui/src/components/dashboard/token-chart.jsx +4 -4
- package/packages/gui/src/components/editor/selection-menu.jsx +2 -0
- package/packages/gui/src/components/lab/lab-assistant.jsx +316 -82
- package/packages/gui/src/components/lab/metrics-panel.jsx +187 -32
- package/packages/gui/src/components/lab/parameter-panel.jsx +195 -14
- package/packages/gui/src/components/lab/runtime-config.jsx +286 -102
- package/packages/gui/src/components/layout/activity-bar.jsx +2 -4
- package/packages/gui/src/components/layout/terminal-panel.jsx +4 -2
- package/packages/gui/src/components/layout/welcome-splash.jsx +137 -108
- package/packages/gui/src/components/network/network-health.jsx +2 -2
- package/packages/gui/src/components/network/performance-dashboard.jsx +4 -4
- package/packages/gui/src/components/settings/ssh-wizard.jsx +81 -99
- package/packages/gui/src/components/ui/sheet.jsx +5 -2
- package/packages/gui/src/lib/cron.js +64 -0
- package/packages/gui/src/lib/status.js +24 -24
- package/packages/gui/src/lib/theme-hex.js +1 -0
- package/packages/gui/src/stores/groove.js +34 -3144
- package/packages/gui/src/stores/helpers.js +10 -0
- package/packages/gui/src/stores/slices/agents-slice.js +452 -0
- package/packages/gui/src/stores/slices/automations-slice.js +96 -0
- package/packages/gui/src/stores/slices/chat-slice.js +227 -0
- package/packages/gui/src/stores/slices/editor-slice.js +285 -0
- package/packages/gui/src/stores/slices/marketplace-slice.js +461 -0
- package/packages/gui/src/stores/slices/network-slice.js +361 -0
- package/packages/gui/src/stores/slices/preview-slice.js +109 -0
- package/packages/gui/src/stores/slices/providers-slice.js +897 -0
- package/packages/gui/src/stores/slices/teams-slice.js +413 -0
- package/packages/gui/src/stores/slices/ui-slice.js +98 -0
- package/packages/gui/src/views/agents.jsx +5 -5
- package/packages/gui/src/views/dashboard.jsx +12 -13
- package/packages/gui/src/views/marketplace.jsx +191 -3
- package/packages/gui/src/views/model-lab.jsx +17 -6
- package/packages/gui/src/views/models.jsx +410 -509
- package/packages/gui/src/views/network.jsx +3 -3
- package/packages/gui/src/views/settings.jsx +81 -94
- package/packages/gui/src/views/teams.jsx +40 -483
- package/SECURITY_SWEEP.md +0 -228
- package/TRAINING_DATA_v4.md +0 -6
- package/node_modules/@groove-dev/gui/dist/assets/index-Bjd91ufV.js +0 -984
- package/node_modules/@groove-dev/gui/dist/assets/index-BqdwIFn4.css +0 -1
- package/node_modules/@groove-dev/gui/src/components/agents/agent-chat.jsx +0 -322
- package/node_modules/@groove-dev/gui/src/views/preview.jsx +0 -6
- package/node_modules/@groove-dev/gui/src/views/subscription-panel.jsx +0 -327
- package/packages/gui/dist/assets/index-Bjd91ufV.js +0 -984
- package/packages/gui/dist/assets/index-BqdwIFn4.css +0 -1
- package/packages/gui/src/components/agents/agent-chat.jsx +0 -322
- package/packages/gui/src/views/preview.jsx +0 -6
- package/packages/gui/src/views/subscription-panel.jsx +0 -327
- package/test.py +0 -571
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
// GROOVE — Agent Scheduler (Cron-based agent spawning)
|
|
1
|
+
// GROOVE — Agent Scheduler (Cron-based agent spawning + Automations)
|
|
2
2
|
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
3
3
|
|
|
4
4
|
import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync, unlinkSync } from 'fs';
|
|
5
5
|
import { resolve } from 'path';
|
|
6
6
|
import { randomUUID } from 'crypto';
|
|
7
7
|
|
|
8
|
-
// Simple cron field parser — supports: *, N, */N
|
|
8
|
+
// Simple cron field parser — supports: *, N, */N, N,N,N (lists), N-N (ranges)
|
|
9
9
|
// Fields: minute(0-59) hour(0-23) dayOfMonth(1-31) month(1-12) dayOfWeek(0-6)
|
|
10
10
|
function parseCronField(field, min, max) {
|
|
11
11
|
if (field === '*') return null; // any
|
|
@@ -14,6 +14,18 @@ function parseCronField(field, min, max) {
|
|
|
14
14
|
if (isNaN(step) || step <= 0) return null;
|
|
15
15
|
return { type: 'step', step };
|
|
16
16
|
}
|
|
17
|
+
if (field.includes(',')) {
|
|
18
|
+
const values = field.split(',').map((v) => parseInt(v.trim(), 10)).filter((v) => !isNaN(v) && v >= min && v <= max);
|
|
19
|
+
if (values.length === 0) return null;
|
|
20
|
+
return { type: 'list', values };
|
|
21
|
+
}
|
|
22
|
+
if (field.includes('-')) {
|
|
23
|
+
const [startStr, endStr] = field.split('-');
|
|
24
|
+
const start = parseInt(startStr, 10);
|
|
25
|
+
const end = parseInt(endStr, 10);
|
|
26
|
+
if (isNaN(start) || isNaN(end) || start < min || end > max) return null;
|
|
27
|
+
return { type: 'range', start, end };
|
|
28
|
+
}
|
|
17
29
|
const val = parseInt(field, 10);
|
|
18
30
|
if (!isNaN(val) && val >= min && val <= max) {
|
|
19
31
|
return { type: 'exact', value: val };
|
|
@@ -25,6 +37,8 @@ function fieldMatches(parsed, value) {
|
|
|
25
37
|
if (parsed === null) return true; // wildcard
|
|
26
38
|
if (parsed.type === 'exact') return value === parsed.value;
|
|
27
39
|
if (parsed.type === 'step') return value % parsed.step === 0;
|
|
40
|
+
if (parsed.type === 'list') return parsed.values.includes(value);
|
|
41
|
+
if (parsed.type === 'range') return value >= parsed.start && value <= parsed.end;
|
|
28
42
|
return true;
|
|
29
43
|
}
|
|
30
44
|
|
|
@@ -65,10 +79,14 @@ function describeCron(cron) {
|
|
|
65
79
|
'0 0 * * 0': 'Weekly (Sunday midnight)',
|
|
66
80
|
'0 0 * * 1': 'Weekly (Monday midnight)',
|
|
67
81
|
'0 0 1 * *': 'Monthly (1st at midnight)',
|
|
82
|
+
'0 9,17 * * *': 'Twice daily (9 AM & 5 PM)',
|
|
83
|
+
'0 9 * * 1,4': 'Monday & Thursday at 9 AM',
|
|
68
84
|
};
|
|
69
85
|
return presets[cron] || cron;
|
|
70
86
|
}
|
|
71
87
|
|
|
88
|
+
export { describeCron };
|
|
89
|
+
|
|
72
90
|
const CHECK_INTERVAL = 60_000; // 1 minute
|
|
73
91
|
const MAX_HISTORY = 50;
|
|
74
92
|
|
|
@@ -78,7 +96,7 @@ export class Scheduler {
|
|
|
78
96
|
this.schedulesDir = resolve(daemon.grooveDir, 'schedules');
|
|
79
97
|
mkdirSync(this.schedulesDir, { recursive: true });
|
|
80
98
|
this.schedules = new Map();
|
|
81
|
-
this.runningAgents = new Map(); // scheduleId -> agentId
|
|
99
|
+
this.runningAgents = new Map(); // scheduleId -> agentId (or Set of agentIds for teams)
|
|
82
100
|
this.history = new Map(); // scheduleId -> [{ timestamp, agentId, status }]
|
|
83
101
|
this.interval = null;
|
|
84
102
|
this._load();
|
|
@@ -90,21 +108,93 @@ export class Scheduler {
|
|
|
90
108
|
create(config) {
|
|
91
109
|
if (!config.name) throw new Error('Schedule name is required');
|
|
92
110
|
if (!config.cron) throw new Error('Cron expression is required');
|
|
93
|
-
|
|
94
|
-
|
|
111
|
+
|
|
112
|
+
// Require either agentConfig or teamConfig
|
|
113
|
+
if (!config.agentConfig && !config.teamConfig) {
|
|
114
|
+
throw new Error('Either agentConfig or teamConfig is required');
|
|
115
|
+
}
|
|
116
|
+
if (config.agentConfig && !config.agentConfig.role) {
|
|
117
|
+
throw new Error('Agent role is required');
|
|
118
|
+
}
|
|
119
|
+
if (config.teamConfig) {
|
|
120
|
+
if (!Array.isArray(config.teamConfig) || config.teamConfig.length === 0) {
|
|
121
|
+
throw new Error('teamConfig must be a non-empty array of agent configs');
|
|
122
|
+
}
|
|
123
|
+
for (const tc of config.teamConfig) {
|
|
124
|
+
if (!tc.role) throw new Error('Each teamConfig entry must have a role');
|
|
125
|
+
}
|
|
126
|
+
}
|
|
95
127
|
|
|
96
128
|
// Validate cron (basic check)
|
|
97
129
|
const parts = config.cron.trim().split(/\s+/);
|
|
98
130
|
if (parts.length !== 5) throw new Error('Cron must have 5 fields: minute hour day month weekday');
|
|
99
131
|
|
|
132
|
+
// Validate instructionSource
|
|
133
|
+
if (config.instructionSource) {
|
|
134
|
+
const is = config.instructionSource;
|
|
135
|
+
if (!['inline', 'file'].includes(is.type)) {
|
|
136
|
+
throw new Error('instructionSource.type must be "inline" or "file"');
|
|
137
|
+
}
|
|
138
|
+
if (is.type === 'inline' && (!is.content || typeof is.content !== 'string')) {
|
|
139
|
+
throw new Error('instructionSource.content is required for inline type');
|
|
140
|
+
}
|
|
141
|
+
if (is.type === 'file' && (!is.filePath || typeof is.filePath !== 'string')) {
|
|
142
|
+
throw new Error('instructionSource.filePath is required for file type');
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Validate outputConfig
|
|
147
|
+
if (config.outputConfig) {
|
|
148
|
+
const oc = config.outputConfig;
|
|
149
|
+
if (oc.gatewayIds && !Array.isArray(oc.gatewayIds)) {
|
|
150
|
+
throw new Error('outputConfig.gatewayIds must be an array');
|
|
151
|
+
}
|
|
152
|
+
if (oc.notifyOn && !['complete', 'error', 'always'].includes(oc.notifyOn)) {
|
|
153
|
+
throw new Error('outputConfig.notifyOn must be "complete", "error", or "always"');
|
|
154
|
+
}
|
|
155
|
+
// Validate gateway IDs reference real gateways
|
|
156
|
+
if (oc.gatewayIds && this.daemon.gateways) {
|
|
157
|
+
for (const gid of oc.gatewayIds) {
|
|
158
|
+
if (!this.daemon.gateways.get(gid)) {
|
|
159
|
+
throw new Error(`Gateway not found: ${gid}`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Validate integrationIds
|
|
166
|
+
if (config.integrationIds) {
|
|
167
|
+
if (!Array.isArray(config.integrationIds)) {
|
|
168
|
+
throw new Error('integrationIds must be an array');
|
|
169
|
+
}
|
|
170
|
+
if (this.daemon.integrations) {
|
|
171
|
+
const installed = this.daemon.integrations.getInstalled();
|
|
172
|
+
for (const iid of config.integrationIds) {
|
|
173
|
+
if (!installed.some((i) => i.id === iid)) {
|
|
174
|
+
throw new Error(`Integration not installed: ${iid}`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
100
180
|
const schedule = {
|
|
101
181
|
id: randomUUID().slice(0, 8),
|
|
102
182
|
name: config.name,
|
|
103
183
|
cron: config.cron.trim(),
|
|
104
184
|
cronDescription: describeCron(config.cron.trim()),
|
|
105
|
-
agentConfig: config.agentConfig,
|
|
185
|
+
agentConfig: config.agentConfig || null,
|
|
186
|
+
teamConfig: config.teamConfig || null,
|
|
187
|
+
instructionSource: config.instructionSource || null,
|
|
188
|
+
outputConfig: config.outputConfig || null,
|
|
189
|
+
integrationIds: config.integrationIds || null,
|
|
190
|
+
description: config.description || '',
|
|
191
|
+
teamName: config.teamName || '',
|
|
106
192
|
enabled: config.enabled !== false,
|
|
107
193
|
maxConcurrent: config.maxConcurrent || 1,
|
|
194
|
+
lastRunStatus: null,
|
|
195
|
+
lastRunAt: null,
|
|
196
|
+
lastRunDuration: null,
|
|
197
|
+
lastRunCost: null,
|
|
108
198
|
createdAt: new Date().toISOString(),
|
|
109
199
|
updatedAt: new Date().toISOString(),
|
|
110
200
|
};
|
|
@@ -113,6 +203,7 @@ export class Scheduler {
|
|
|
113
203
|
this.history.set(schedule.id, []);
|
|
114
204
|
this._save(schedule.id);
|
|
115
205
|
|
|
206
|
+
this.daemon.broadcast({ type: 'schedule:created', data: schedule });
|
|
116
207
|
this.daemon.audit.log('schedule.create', { id: schedule.id, name: schedule.name, cron: schedule.cron });
|
|
117
208
|
|
|
118
209
|
return schedule;
|
|
@@ -125,7 +216,11 @@ export class Scheduler {
|
|
|
125
216
|
const schedule = this.schedules.get(id);
|
|
126
217
|
if (!schedule) throw new Error(`Schedule not found: ${id}`);
|
|
127
218
|
|
|
128
|
-
const SAFE = [
|
|
219
|
+
const SAFE = [
|
|
220
|
+
'name', 'cron', 'agentConfig', 'teamConfig', 'instructionSource',
|
|
221
|
+
'outputConfig', 'integrationIds', 'description', 'teamName',
|
|
222
|
+
'enabled', 'maxConcurrent',
|
|
223
|
+
];
|
|
129
224
|
for (const key of Object.keys(updates)) {
|
|
130
225
|
if (SAFE.includes(key)) {
|
|
131
226
|
schedule[key] = updates[key];
|
|
@@ -137,6 +232,7 @@ export class Scheduler {
|
|
|
137
232
|
schedule.updatedAt = new Date().toISOString();
|
|
138
233
|
this._save(id);
|
|
139
234
|
|
|
235
|
+
this.daemon.broadcast({ type: 'schedule:updated', data: schedule });
|
|
140
236
|
return schedule;
|
|
141
237
|
}
|
|
142
238
|
|
|
@@ -152,6 +248,7 @@ export class Scheduler {
|
|
|
152
248
|
const filePath = resolve(this.schedulesDir, `${id}.json`);
|
|
153
249
|
if (existsSync(filePath)) unlinkSync(filePath);
|
|
154
250
|
|
|
251
|
+
this.daemon.broadcast({ type: 'schedule:deleted', data: { id } });
|
|
155
252
|
this.daemon.audit.log('schedule.delete', { id });
|
|
156
253
|
}
|
|
157
254
|
|
|
@@ -164,6 +261,7 @@ export class Scheduler {
|
|
|
164
261
|
schedule.enabled = true;
|
|
165
262
|
schedule.updatedAt = new Date().toISOString();
|
|
166
263
|
this._save(id);
|
|
264
|
+
this.daemon.broadcast({ type: 'schedule:updated', data: schedule });
|
|
167
265
|
return schedule;
|
|
168
266
|
}
|
|
169
267
|
|
|
@@ -176,6 +274,7 @@ export class Scheduler {
|
|
|
176
274
|
schedule.enabled = false;
|
|
177
275
|
schedule.updatedAt = new Date().toISOString();
|
|
178
276
|
this._save(id);
|
|
277
|
+
this.daemon.broadcast({ type: 'schedule:updated', data: schedule });
|
|
179
278
|
return schedule;
|
|
180
279
|
}
|
|
181
280
|
|
|
@@ -183,11 +282,20 @@ export class Scheduler {
|
|
|
183
282
|
* List all schedules with their current state.
|
|
184
283
|
*/
|
|
185
284
|
list() {
|
|
186
|
-
return Array.from(this.schedules.values()).map((s) =>
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
285
|
+
return Array.from(this.schedules.values()).map((s) => {
|
|
286
|
+
const runInfo = this.runningAgents.get(s.id);
|
|
287
|
+
let activeAgentIds = null;
|
|
288
|
+
if (runInfo) {
|
|
289
|
+
if (typeof runInfo === 'string') activeAgentIds = [runInfo];
|
|
290
|
+
else if (runInfo.agentIds) activeAgentIds = runInfo.agentIds;
|
|
291
|
+
}
|
|
292
|
+
return {
|
|
293
|
+
...s,
|
|
294
|
+
lastRun: this._lastRun(s.id),
|
|
295
|
+
isRunning: this.runningAgents.has(s.id),
|
|
296
|
+
activeAgentIds,
|
|
297
|
+
};
|
|
298
|
+
});
|
|
191
299
|
}
|
|
192
300
|
|
|
193
301
|
/**
|
|
@@ -196,14 +304,48 @@ export class Scheduler {
|
|
|
196
304
|
get(id) {
|
|
197
305
|
const schedule = this.schedules.get(id);
|
|
198
306
|
if (!schedule) return null;
|
|
307
|
+
const runInfo = this.runningAgents.get(id);
|
|
308
|
+
let activeAgentIds = null;
|
|
309
|
+
if (runInfo) {
|
|
310
|
+
if (typeof runInfo === 'string') activeAgentIds = [runInfo];
|
|
311
|
+
else if (runInfo.agentIds) activeAgentIds = runInfo.agentIds;
|
|
312
|
+
}
|
|
199
313
|
return {
|
|
200
314
|
...schedule,
|
|
201
315
|
history: this.history.get(id) || [],
|
|
202
316
|
lastRun: this._lastRun(id),
|
|
203
317
|
isRunning: this.runningAgents.has(id),
|
|
318
|
+
activeAgentIds,
|
|
204
319
|
};
|
|
205
320
|
}
|
|
206
321
|
|
|
322
|
+
/**
|
|
323
|
+
* Get run history for a schedule.
|
|
324
|
+
*/
|
|
325
|
+
getRunHistory(id) {
|
|
326
|
+
if (!this.schedules.has(id)) return null;
|
|
327
|
+
return this.history.get(id) || [];
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Duplicate a schedule with a new ID.
|
|
332
|
+
*/
|
|
333
|
+
duplicate(id) {
|
|
334
|
+
const original = this.schedules.get(id);
|
|
335
|
+
if (!original) throw new Error(`Schedule not found: ${id}`);
|
|
336
|
+
|
|
337
|
+
const clone = {
|
|
338
|
+
...original,
|
|
339
|
+
name: `${original.name} (Copy)`,
|
|
340
|
+
lastRunStatus: null,
|
|
341
|
+
lastRunAt: null,
|
|
342
|
+
lastRunDuration: null,
|
|
343
|
+
lastRunCost: null,
|
|
344
|
+
};
|
|
345
|
+
// create() will assign a new ID and timestamps
|
|
346
|
+
return this.create(clone);
|
|
347
|
+
}
|
|
348
|
+
|
|
207
349
|
/**
|
|
208
350
|
* Manually trigger a schedule (run now).
|
|
209
351
|
*/
|
|
@@ -240,14 +382,25 @@ export class Scheduler {
|
|
|
240
382
|
if (cronMatches(schedule.cron, now)) {
|
|
241
383
|
// Check concurrency
|
|
242
384
|
if (this.runningAgents.has(schedule.id)) {
|
|
243
|
-
const
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
this.
|
|
248
|
-
|
|
385
|
+
const runInfo = this.runningAgents.get(schedule.id);
|
|
386
|
+
// For team runs, runInfo is { agentIds, teamId, startedAt }
|
|
387
|
+
// For single agent runs, it's an agentId string (backward compat)
|
|
388
|
+
if (typeof runInfo === 'string') {
|
|
389
|
+
const agent = this.daemon.registry.get(runInfo);
|
|
390
|
+
if (agent && (agent.status === 'running' || agent.status === 'starting')) {
|
|
391
|
+
this._recordHistory(schedule.id, null, 'skipped');
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
} else if (runInfo && runInfo.agentIds) {
|
|
395
|
+
const anyRunning = runInfo.agentIds.some((aid) => {
|
|
396
|
+
const a = this.daemon.registry.get(aid);
|
|
397
|
+
return a && (a.status === 'running' || a.status === 'starting');
|
|
398
|
+
});
|
|
399
|
+
if (anyRunning) {
|
|
400
|
+
this._recordHistory(schedule.id, null, 'skipped');
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
249
403
|
}
|
|
250
|
-
// Agent finished — clear
|
|
251
404
|
this.runningAgents.delete(schedule.id);
|
|
252
405
|
}
|
|
253
406
|
this._execute(schedule).catch(() => {});
|
|
@@ -255,12 +408,74 @@ export class Scheduler {
|
|
|
255
408
|
}
|
|
256
409
|
}
|
|
257
410
|
|
|
411
|
+
/**
|
|
412
|
+
* Resolve the instruction prompt for this schedule.
|
|
413
|
+
*/
|
|
414
|
+
_resolveInstruction(schedule) {
|
|
415
|
+
let instruction = null;
|
|
416
|
+
if (schedule.instructionSource) {
|
|
417
|
+
const is = schedule.instructionSource;
|
|
418
|
+
if (is.type === 'inline') instruction = is.content;
|
|
419
|
+
if (is.type === 'file') {
|
|
420
|
+
if (!existsSync(is.filePath)) {
|
|
421
|
+
throw new Error(`Instruction file not found: ${is.filePath}`);
|
|
422
|
+
}
|
|
423
|
+
instruction = readFileSync(is.filePath, 'utf8');
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const oc = schedule.outputConfig;
|
|
428
|
+
if (oc) {
|
|
429
|
+
const parts = [];
|
|
430
|
+
if (oc.filePath) parts.push(`Save output/results to: ${oc.filePath}`);
|
|
431
|
+
if (oc.customInstructions) parts.push(oc.customInstructions);
|
|
432
|
+
if (parts.length > 0) {
|
|
433
|
+
const section = '\n\n## Output\n' + parts.join('\n');
|
|
434
|
+
instruction = instruction ? instruction + section : section;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
return instruction;
|
|
439
|
+
}
|
|
440
|
+
|
|
258
441
|
async _execute(schedule) {
|
|
442
|
+
const startedAt = Date.now();
|
|
443
|
+
|
|
444
|
+
// Update run status
|
|
445
|
+
schedule.lastRunStatus = 'running';
|
|
446
|
+
schedule.lastRunAt = new Date().toISOString();
|
|
447
|
+
this._save(schedule.id);
|
|
448
|
+
|
|
449
|
+
// Resolve instruction prompt
|
|
450
|
+
let instruction;
|
|
259
451
|
try {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
452
|
+
instruction = this._resolveInstruction(schedule);
|
|
453
|
+
} catch (err) {
|
|
454
|
+
this._recordHistory(schedule.id, null, 'error', err.message);
|
|
455
|
+
schedule.lastRunStatus = 'error';
|
|
456
|
+
schedule.lastRunDuration = Date.now() - startedAt;
|
|
457
|
+
this._save(schedule.id);
|
|
458
|
+
throw err;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// Team-based execution
|
|
462
|
+
if (schedule.teamConfig) {
|
|
463
|
+
return this._executeTeam(schedule, instruction, startedAt);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Single agent execution (backward compatible)
|
|
467
|
+
return this._executeSingle(schedule, instruction, startedAt);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
async _executeSingle(schedule, instruction, startedAt) {
|
|
471
|
+
try {
|
|
472
|
+
const config = { ...schedule.agentConfig };
|
|
473
|
+
if (instruction) config.prompt = instruction;
|
|
474
|
+
if (schedule.integrationIds) config.integrations = schedule.integrationIds;
|
|
475
|
+
config.name = `sched-${schedule.name.replace(/[^a-zA-Z0-9_-]/g, '_').slice(0, 20)}`;
|
|
476
|
+
config.metadata = { ...config.metadata, scheduled: true, scheduleId: schedule.id };
|
|
477
|
+
|
|
478
|
+
const agent = await this.daemon.processes.spawn(config);
|
|
264
479
|
this.runningAgents.set(schedule.id, agent.id);
|
|
265
480
|
this._recordHistory(schedule.id, agent.id, 'spawned');
|
|
266
481
|
|
|
@@ -276,13 +491,220 @@ export class Scheduler {
|
|
|
276
491
|
agentId: agent.id,
|
|
277
492
|
});
|
|
278
493
|
|
|
494
|
+
// Watch for completion to update run metadata and send notifications
|
|
495
|
+
this._watchAgent(schedule, agent.id, startedAt);
|
|
496
|
+
|
|
279
497
|
return agent;
|
|
280
498
|
} catch (err) {
|
|
281
499
|
this._recordHistory(schedule.id, null, 'error', err.message);
|
|
500
|
+
schedule.lastRunStatus = 'error';
|
|
501
|
+
schedule.lastRunDuration = Date.now() - startedAt;
|
|
502
|
+
this._save(schedule.id);
|
|
503
|
+
this._sendOutputNotification(schedule, 'error', startedAt, err.message);
|
|
504
|
+
throw err;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
async _executeTeam(schedule, instruction, startedAt) {
|
|
509
|
+
try {
|
|
510
|
+
// Create a team for this run
|
|
511
|
+
const teamName = schedule.teamName || schedule.name;
|
|
512
|
+
let team;
|
|
513
|
+
try {
|
|
514
|
+
team = this.daemon.teams.create(teamName);
|
|
515
|
+
} catch {
|
|
516
|
+
// Team name might already exist, use a unique suffix
|
|
517
|
+
team = this.daemon.teams.create(`${teamName}-${Date.now().toString(36)}`);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
const defaultProvider = this.daemon.config?.defaultProvider || 'claude-code';
|
|
521
|
+
const defaultDir = this.daemon.config?.defaultWorkingDir || this.daemon.projectDir;
|
|
522
|
+
|
|
523
|
+
// Separate phases
|
|
524
|
+
const phase1Configs = schedule.teamConfig.filter((c) => !c.phase || c.phase === 1);
|
|
525
|
+
const phase2Configs = schedule.teamConfig.filter((c) => c.phase === 2);
|
|
526
|
+
|
|
527
|
+
const allAgentIds = [];
|
|
528
|
+
const phase1Ids = [];
|
|
529
|
+
|
|
530
|
+
// Spawn phase 1 agents
|
|
531
|
+
for (const tc of phase1Configs) {
|
|
532
|
+
const config = {
|
|
533
|
+
role: tc.role,
|
|
534
|
+
scope: tc.scope || [],
|
|
535
|
+
provider: tc.provider || defaultProvider,
|
|
536
|
+
model: tc.model || 'auto',
|
|
537
|
+
permission: tc.permission || 'auto',
|
|
538
|
+
workingDir: defaultDir,
|
|
539
|
+
name: tc.name || undefined,
|
|
540
|
+
};
|
|
541
|
+
if (instruction) config.prompt = instruction;
|
|
542
|
+
else if (tc.prompt) config.prompt = tc.prompt;
|
|
543
|
+
if (schedule.integrationIds) config.integrations = schedule.integrationIds;
|
|
544
|
+
config.teamId = team.id;
|
|
545
|
+
config.metadata = { scheduled: true, scheduleId: schedule.id };
|
|
546
|
+
|
|
547
|
+
try {
|
|
548
|
+
const agent = await this.daemon.processes.spawn(config);
|
|
549
|
+
phase1Ids.push(agent.id);
|
|
550
|
+
allAgentIds.push(agent.id);
|
|
551
|
+
} catch (err) {
|
|
552
|
+
console.log(`[Groove:Scheduler] Failed to spawn ${tc.role}: ${err.message}`);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
if (phase1Ids.length === 0) {
|
|
557
|
+
throw new Error('Failed to spawn any phase 1 agents');
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// Register phase 2 for auto-spawn
|
|
561
|
+
if (phase2Configs.length > 0) {
|
|
562
|
+
this.daemon._pendingPhase2 = this.daemon._pendingPhase2 || [];
|
|
563
|
+
this.daemon._pendingPhase2.push({
|
|
564
|
+
waitFor: phase1Ids,
|
|
565
|
+
agents: phase2Configs.map((c) => ({
|
|
566
|
+
role: c.role,
|
|
567
|
+
scope: c.scope || [],
|
|
568
|
+
prompt: c.prompt || (instruction ? instruction : ''),
|
|
569
|
+
provider: c.provider || defaultProvider,
|
|
570
|
+
model: c.model || 'auto',
|
|
571
|
+
permission: c.permission || 'auto',
|
|
572
|
+
workingDir: defaultDir,
|
|
573
|
+
name: c.name || undefined,
|
|
574
|
+
teamId: team.id,
|
|
575
|
+
})),
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
this.runningAgents.set(schedule.id, {
|
|
580
|
+
agentIds: allAgentIds,
|
|
581
|
+
teamId: team.id,
|
|
582
|
+
startedAt,
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
this._recordHistory(schedule.id, phase1Ids.join(','), 'spawned');
|
|
586
|
+
|
|
587
|
+
this.daemon.broadcast({
|
|
588
|
+
type: 'schedule:execute',
|
|
589
|
+
scheduleId: schedule.id,
|
|
590
|
+
teamId: team.id,
|
|
591
|
+
agentCount: phase1Ids.length,
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
this.daemon.audit.log('schedule.executeTeam', {
|
|
595
|
+
id: schedule.id,
|
|
596
|
+
name: schedule.name,
|
|
597
|
+
teamId: team.id,
|
|
598
|
+
phase1: phase1Ids.length,
|
|
599
|
+
phase2Pending: phase2Configs.length,
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
// Watch all agents for completion
|
|
603
|
+
for (const aid of allAgentIds) {
|
|
604
|
+
this._watchAgent(schedule, aid, startedAt);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
return { teamId: team.id, agentIds: allAgentIds };
|
|
608
|
+
} catch (err) {
|
|
609
|
+
this._recordHistory(schedule.id, null, 'error', err.message);
|
|
610
|
+
schedule.lastRunStatus = 'error';
|
|
611
|
+
schedule.lastRunDuration = Date.now() - startedAt;
|
|
612
|
+
this._save(schedule.id);
|
|
613
|
+
this._sendOutputNotification(schedule, 'error', startedAt, err.message);
|
|
282
614
|
throw err;
|
|
283
615
|
}
|
|
284
616
|
}
|
|
285
617
|
|
|
618
|
+
/**
|
|
619
|
+
* Watch an agent for completion and update run metadata.
|
|
620
|
+
*/
|
|
621
|
+
_watchAgent(schedule, agentId, startedAt) {
|
|
622
|
+
const checkInterval = setInterval(() => {
|
|
623
|
+
const agent = this.daemon.registry.get(agentId);
|
|
624
|
+
if (!agent) {
|
|
625
|
+
clearInterval(checkInterval);
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
const terminal = new Set(['completed', 'crashed', 'stopped', 'killed']);
|
|
630
|
+
if (!terminal.has(agent.status)) return;
|
|
631
|
+
|
|
632
|
+
clearInterval(checkInterval);
|
|
633
|
+
|
|
634
|
+
const runInfo = this.runningAgents.get(schedule.id);
|
|
635
|
+
|
|
636
|
+
// For team runs, check if ALL agents are done
|
|
637
|
+
if (runInfo && typeof runInfo === 'object' && runInfo.agentIds) {
|
|
638
|
+
const allDone = runInfo.agentIds.every((aid) => {
|
|
639
|
+
const a = this.daemon.registry.get(aid);
|
|
640
|
+
return !a || terminal.has(a.status);
|
|
641
|
+
});
|
|
642
|
+
if (!allDone) return;
|
|
643
|
+
|
|
644
|
+
// Also check for phase 2 agents that were spawned after
|
|
645
|
+
const teamAgents = this.daemon.registry.getAll().filter((a) => a.teamId === runInfo.teamId);
|
|
646
|
+
const teamAllDone = teamAgents.every((a) => terminal.has(a.status));
|
|
647
|
+
if (!teamAllDone) return;
|
|
648
|
+
|
|
649
|
+
// All team agents done
|
|
650
|
+
const anyError = teamAgents.some((a) => a.status === 'crashed');
|
|
651
|
+
const totalCost = teamAgents.reduce((sum, a) => sum + (a.costUsd || 0), 0);
|
|
652
|
+
|
|
653
|
+
schedule.lastRunStatus = anyError ? 'error' : 'success';
|
|
654
|
+
schedule.lastRunDuration = Date.now() - startedAt;
|
|
655
|
+
schedule.lastRunCost = totalCost;
|
|
656
|
+
this._save(schedule.id);
|
|
657
|
+
this.runningAgents.delete(schedule.id);
|
|
658
|
+
|
|
659
|
+
this._recordHistory(schedule.id, runInfo.agentIds.join(','), anyError ? 'error' : 'completed');
|
|
660
|
+
this._sendOutputNotification(schedule, anyError ? 'error' : 'success', startedAt,
|
|
661
|
+
anyError ? `${teamAgents.filter((a) => a.status === 'crashed').length} agent(s) crashed` : null);
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// Single agent completion
|
|
666
|
+
schedule.lastRunStatus = agent.status === 'completed' ? 'success' : 'error';
|
|
667
|
+
schedule.lastRunDuration = Date.now() - startedAt;
|
|
668
|
+
schedule.lastRunCost = agent.costUsd || 0;
|
|
669
|
+
this._save(schedule.id);
|
|
670
|
+
this.runningAgents.delete(schedule.id);
|
|
671
|
+
|
|
672
|
+
this._recordHistory(schedule.id, agentId, agent.status === 'completed' ? 'completed' : 'error');
|
|
673
|
+
this._sendOutputNotification(schedule,
|
|
674
|
+
agent.status === 'completed' ? 'success' : 'error',
|
|
675
|
+
startedAt,
|
|
676
|
+
agent.status !== 'completed' ? `Agent ${agent.status}` : null);
|
|
677
|
+
}, 5000);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
/**
|
|
681
|
+
* Send output notification through configured gateways.
|
|
682
|
+
*/
|
|
683
|
+
_sendOutputNotification(schedule, status, startedAt, errorDetails) {
|
|
684
|
+
if (!schedule.outputConfig || !schedule.outputConfig.gatewayIds || schedule.outputConfig.gatewayIds.length === 0) return;
|
|
685
|
+
|
|
686
|
+
const notifyOn = schedule.outputConfig.notifyOn || 'always';
|
|
687
|
+
if (notifyOn === 'complete' && status !== 'success') return;
|
|
688
|
+
if (notifyOn === 'error' && status !== 'error') return;
|
|
689
|
+
|
|
690
|
+
const duration = Date.now() - startedAt;
|
|
691
|
+
const agentCount = schedule.teamConfig ? schedule.teamConfig.length : 1;
|
|
692
|
+
|
|
693
|
+
const summary = {
|
|
694
|
+
name: schedule.name,
|
|
695
|
+
description: schedule.description || '',
|
|
696
|
+
status,
|
|
697
|
+
duration,
|
|
698
|
+
cost: schedule.lastRunCost || 0,
|
|
699
|
+
agentCount,
|
|
700
|
+
errors: errorDetails || null,
|
|
701
|
+
};
|
|
702
|
+
|
|
703
|
+
if (this.daemon.gateways && typeof this.daemon.gateways.sendScheduleNotification === 'function') {
|
|
704
|
+
this.daemon.gateways.sendScheduleNotification(schedule.outputConfig.gatewayIds, summary);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
|
|
286
708
|
_recordHistory(scheduleId, agentId, status, error) {
|
|
287
709
|
const history = this.history.get(scheduleId) || [];
|
|
288
710
|
history.unshift({
|
|
@@ -323,9 +745,19 @@ export class Scheduler {
|
|
|
323
745
|
name: data.name,
|
|
324
746
|
cron: data.cron,
|
|
325
747
|
cronDescription: describeCron(data.cron),
|
|
326
|
-
agentConfig: data.agentConfig,
|
|
748
|
+
agentConfig: data.agentConfig || null,
|
|
749
|
+
teamConfig: data.teamConfig || null,
|
|
750
|
+
instructionSource: data.instructionSource || null,
|
|
751
|
+
outputConfig: data.outputConfig || null,
|
|
752
|
+
integrationIds: data.integrationIds || null,
|
|
753
|
+
description: data.description || '',
|
|
754
|
+
teamName: data.teamName || '',
|
|
327
755
|
enabled: data.enabled !== false,
|
|
328
756
|
maxConcurrent: data.maxConcurrent || 1,
|
|
757
|
+
lastRunStatus: data.lastRunStatus || null,
|
|
758
|
+
lastRunAt: data.lastRunAt || null,
|
|
759
|
+
lastRunDuration: data.lastRunDuration || null,
|
|
760
|
+
lastRunCost: data.lastRunCost || null,
|
|
329
761
|
createdAt: data.createdAt,
|
|
330
762
|
updatedAt: data.updatedAt,
|
|
331
763
|
});
|
|
@@ -371,7 +371,7 @@ export class Teams {
|
|
|
371
371
|
const defaultTeam = this.getDefault();
|
|
372
372
|
if (!defaultTeam) return;
|
|
373
373
|
for (const agent of this.daemon.registry.getAll()) {
|
|
374
|
-
if (!agent.teamId) {
|
|
374
|
+
if (!agent.teamId && !agent.metadata?.scheduled) {
|
|
375
375
|
this.daemon.registry.update(agent.id, { teamId: defaultTeam.id });
|
|
376
376
|
}
|
|
377
377
|
}
|