ai-control-center 1.15.2

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 (154) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +584 -0
  3. package/bin/aicc.js +772 -0
  4. package/lib/actions/approve.js +71 -0
  5. package/lib/actions/assign-project.js +132 -0
  6. package/lib/actions/browser-test.js +64 -0
  7. package/lib/actions/cleanup.js +174 -0
  8. package/lib/actions/debug.js +298 -0
  9. package/lib/actions/deploy.js +1229 -0
  10. package/lib/actions/fix-bug.js +134 -0
  11. package/lib/actions/new-feature.js +255 -0
  12. package/lib/actions/reject.js +307 -0
  13. package/lib/actions/review.js +706 -0
  14. package/lib/actions/status.js +47 -0
  15. package/lib/agents/browser-qa-agent.js +611 -0
  16. package/lib/agents/payment-agent.js +116 -0
  17. package/lib/agents/suggestion-agent.js +88 -0
  18. package/lib/cli.js +303 -0
  19. package/lib/config.js +243 -0
  20. package/lib/hub/hub-server.js +440 -0
  21. package/lib/hub/project-poller.js +75 -0
  22. package/lib/hub/skill-registry.js +89 -0
  23. package/lib/hub/state-aggregator.js +204 -0
  24. package/lib/index.js +471 -0
  25. package/lib/init/doctor.js +523 -0
  26. package/lib/init/presets.js +222 -0
  27. package/lib/init/skill-fetcher.js +77 -0
  28. package/lib/init/wizard.js +973 -0
  29. package/lib/integrations/codex-runner.js +128 -0
  30. package/lib/integrations/github-actions.js +248 -0
  31. package/lib/integrations/github-reporter.js +229 -0
  32. package/lib/integrations/screenshot-store.js +102 -0
  33. package/lib/openclaw/bridge.js +650 -0
  34. package/lib/openclaw/generate-skill.js +235 -0
  35. package/lib/openclaw/openclaw.json +64 -0
  36. package/lib/orchestrator/autonomous-loop.js +429 -0
  37. package/lib/orchestrator/thread-triggers.js +63 -0
  38. package/lib/roleplay/agent-messenger.js +75 -0
  39. package/lib/roleplay/discussion-threads.js +303 -0
  40. package/lib/roleplay/health-monitor.js +121 -0
  41. package/lib/roleplay/pm-agent.js +513 -0
  42. package/lib/roleplay/roleplay-config.js +25 -0
  43. package/lib/roleplay/room.js +164 -0
  44. package/lib/shared/action-runner.js +2330 -0
  45. package/lib/shared/event-bus.js +185 -0
  46. package/lib/slack/bot.js +378 -0
  47. package/lib/telegram/bot.js +416 -0
  48. package/lib/telegram/commands.js +1267 -0
  49. package/lib/telegram/keyboards.js +113 -0
  50. package/lib/telegram/notifications.js +247 -0
  51. package/lib/twitch/bot.js +354 -0
  52. package/lib/twitch/commands.js +302 -0
  53. package/lib/twitch/notifications.js +63 -0
  54. package/lib/utils/achievements.js +191 -0
  55. package/lib/utils/activity-log.js +182 -0
  56. package/lib/utils/agent-leaderboard.js +119 -0
  57. package/lib/utils/audit-logger.js +232 -0
  58. package/lib/utils/codebase-context.js +288 -0
  59. package/lib/utils/codebase-indexer.js +381 -0
  60. package/lib/utils/config-schema.js +230 -0
  61. package/lib/utils/context-compressor.js +172 -0
  62. package/lib/utils/correlation.js +63 -0
  63. package/lib/utils/cost-tracker.js +423 -0
  64. package/lib/utils/cron-scheduler.js +53 -0
  65. package/lib/utils/db-adapter.js +293 -0
  66. package/lib/utils/display.js +272 -0
  67. package/lib/utils/errors.js +116 -0
  68. package/lib/utils/format.js +134 -0
  69. package/lib/utils/intent-engine.js +464 -0
  70. package/lib/utils/mcp-client.js +238 -0
  71. package/lib/utils/model-ab-test.js +164 -0
  72. package/lib/utils/notify.js +122 -0
  73. package/lib/utils/persona-loader.js +80 -0
  74. package/lib/utils/pipeline-lock.js +73 -0
  75. package/lib/utils/pipeline.js +214 -0
  76. package/lib/utils/plugin-runner.js +234 -0
  77. package/lib/utils/rate-limiter.js +84 -0
  78. package/lib/utils/rbac.js +74 -0
  79. package/lib/utils/runner.js +1809 -0
  80. package/lib/utils/security.js +191 -0
  81. package/lib/utils/self-healer.js +144 -0
  82. package/lib/utils/skill-loader.js +255 -0
  83. package/lib/utils/spinner.js +132 -0
  84. package/lib/utils/stage-queue.js +50 -0
  85. package/lib/utils/state-machine.js +89 -0
  86. package/lib/utils/status-bar.js +327 -0
  87. package/lib/utils/token-estimator.js +101 -0
  88. package/lib/utils/ux-analyzer.js +101 -0
  89. package/lib/utils/webhook-emitter.js +83 -0
  90. package/lib/web/public/css/styles.css +417 -0
  91. package/lib/web/public/dark-mode.js +44 -0
  92. package/lib/web/public/hub/kanban.html +206 -0
  93. package/lib/web/public/index.html +45 -0
  94. package/lib/web/public/js/app.js +71 -0
  95. package/lib/web/public/js/ask.js +110 -0
  96. package/lib/web/public/js/dashboard.js +165 -0
  97. package/lib/web/public/js/deploy.js +72 -0
  98. package/lib/web/public/js/feature.js +79 -0
  99. package/lib/web/public/js/health.js +65 -0
  100. package/lib/web/public/js/logs.js +93 -0
  101. package/lib/web/public/js/review.js +123 -0
  102. package/lib/web/public/js/ws-client.js +82 -0
  103. package/lib/web/public/office/css/office.css +678 -0
  104. package/lib/web/public/office/index.html +148 -0
  105. package/lib/web/public/office/js/achievements-ui.js +117 -0
  106. package/lib/web/public/office/js/character.js +1056 -0
  107. package/lib/web/public/office/js/chat-bubbles.js +177 -0
  108. package/lib/web/public/office/js/cost-overlay.js +123 -0
  109. package/lib/web/public/office/js/day-night.js +68 -0
  110. package/lib/web/public/office/js/effects.js +632 -0
  111. package/lib/web/public/office/js/engine.js +146 -0
  112. package/lib/web/public/office/js/feature-ticket.js +216 -0
  113. package/lib/web/public/office/js/hub-client.js +60 -0
  114. package/lib/web/public/office/js/main.js +1757 -0
  115. package/lib/web/public/office/js/office-layout.js +1524 -0
  116. package/lib/web/public/office/js/pathfinding.js +144 -0
  117. package/lib/web/public/office/js/pixel-sprites.js +1454 -0
  118. package/lib/web/public/office/js/progress-bars.js +117 -0
  119. package/lib/web/public/office/js/replay.js +191 -0
  120. package/lib/web/public/office/js/sound-effects.js +91 -0
  121. package/lib/web/public/office/js/sprite-renderer.js +211 -0
  122. package/lib/web/public/office/js/stamina-system.js +89 -0
  123. package/lib/web/public/office/js/ui.js +107 -0
  124. package/lib/web/public/onboarding/index.html +243 -0
  125. package/lib/web/public/timeline/index.html +195 -0
  126. package/lib/web/routes/api.js +499 -0
  127. package/lib/web/routes/logs.js +20 -0
  128. package/lib/web/routes/metrics.js +99 -0
  129. package/lib/web/server.js +183 -0
  130. package/lib/web/ws/handler.js +65 -0
  131. package/package.json +67 -0
  132. package/templates/agent-architect.md +69 -0
  133. package/templates/agent-gemini-pm.md +49 -0
  134. package/templates/agent-gemini-reviewer.md +52 -0
  135. package/templates/copilot-instructions.md +36 -0
  136. package/templates/pipelines/mobile.json +27 -0
  137. package/templates/pipelines/nodejs-api.json +27 -0
  138. package/templates/pipelines/python.json +27 -0
  139. package/templates/pipelines/react.json +27 -0
  140. package/templates/pipelines/salesforce.json +27 -0
  141. package/templates/role-gemini.md +97 -0
  142. package/templates/skill-architect.md +114 -0
  143. package/templates/skill-browser-qa.md +50 -0
  144. package/templates/skill-bug-from-qa.md +58 -0
  145. package/templates/skill-chatbot.md +93 -0
  146. package/templates/skill-implement.md +78 -0
  147. package/templates/skill-openclaw.md +174 -0
  148. package/templates/skill-payment.md +110 -0
  149. package/templates/skill-pm-spec.md +77 -0
  150. package/templates/skill-requirement-capture.md +97 -0
  151. package/templates/skill-review.md +108 -0
  152. package/templates/skill-reviewer-qa.md +44 -0
  153. package/templates/skill-suggestion.md +45 -0
  154. package/templates/skill-template.md +142 -0
@@ -0,0 +1,234 @@
1
+ /**
2
+ * Plugin Stage Architecture — extensible pipeline hooks.
3
+ *
4
+ * Plugins can hook into any pipeline stage to run custom logic
5
+ * before or after the stage executes. Supports lifecycle hooks,
6
+ * custom commands, and event-driven execution.
7
+ *
8
+ * Plugin format in aicc.config.js:
9
+ * plugins: [
10
+ * {
11
+ * name: 'my-plugin',
12
+ * stages: ['review', 'deploy'], // which stages to hook into
13
+ * beforeStage: async (ctx) => { }, // runs before stage
14
+ * afterStage: async (ctx) => { }, // runs after stage
15
+ * commands: { '/mycommand': handler }, // custom Telegram/CLI commands
16
+ * onEvent: { 'feature_approved': handler }, // event listeners
17
+ * }
18
+ * ]
19
+ *
20
+ * Or as a path to a module:
21
+ * plugins: ['./plugins/my-plugin.js']
22
+ */
23
+ import { existsSync } from 'fs';
24
+ import { resolve } from 'path';
25
+ import { getConfig } from '../config.js';
26
+ import { audit, AUDIT_EVENTS } from './audit-logger.js';
27
+
28
+ // ─── Plugin Registry ─────────────────────────────────────────────────────────
29
+
30
+ let _loadedPlugins = [];
31
+ let _initialized = false;
32
+
33
+ /**
34
+ * Load and initialize all plugins from config.
35
+ *
36
+ * @returns {Array} Loaded plugin objects
37
+ */
38
+ export async function loadPlugins() {
39
+ if (_initialized) return _loadedPlugins;
40
+
41
+ try {
42
+ const config = getConfig();
43
+ const pluginDefs = config.plugins || [];
44
+ _loadedPlugins = [];
45
+
46
+ for (const def of pluginDefs) {
47
+ try {
48
+ let plugin;
49
+
50
+ if (typeof def === 'string') {
51
+ // Path to a plugin module
52
+ const pluginPath = resolve(config._root || '.', def);
53
+ if (!existsSync(pluginPath)) {
54
+ audit(AUDIT_EVENTS.PLUGIN_ERROR, { name: def, error: 'File not found' });
55
+ continue;
56
+ }
57
+ const mod = await import(pluginPath);
58
+ plugin = mod.default || mod;
59
+ } else if (typeof def === 'object') {
60
+ plugin = def;
61
+ } else {
62
+ continue;
63
+ }
64
+
65
+ // Validate plugin
66
+ if (!plugin.name) {
67
+ plugin.name = `plugin-${_loadedPlugins.length + 1}`;
68
+ }
69
+
70
+ _loadedPlugins.push({
71
+ name: plugin.name,
72
+ stages: plugin.stages || [],
73
+ beforeStage: plugin.beforeStage || null,
74
+ afterStage: plugin.afterStage || null,
75
+ commands: plugin.commands || {},
76
+ onEvent: plugin.onEvent || {},
77
+ enabled: plugin.enabled !== false,
78
+ });
79
+
80
+ audit(AUDIT_EVENTS.PLUGIN_LOADED, { name: plugin.name, stages: plugin.stages });
81
+ } catch (err) {
82
+ audit(AUDIT_EVENTS.PLUGIN_ERROR, {
83
+ name: typeof def === 'string' ? def : def?.name || 'unknown',
84
+ error: err.message,
85
+ });
86
+ }
87
+ }
88
+
89
+ _initialized = true;
90
+ return _loadedPlugins;
91
+ } catch {
92
+ _initialized = true;
93
+ return _loadedPlugins;
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Run beforeStage hooks for all plugins registered for this stage.
99
+ *
100
+ * @param {string} stage - Pipeline stage name
101
+ * @param {object} context - Stage context (prompt, featureId, config, etc.)
102
+ * @returns {object} Modified context (plugins can mutate it)
103
+ */
104
+ export async function runBeforeStage(stage, context = {}) {
105
+ const plugins = await loadPlugins();
106
+ const applicable = plugins.filter(p => p.enabled && p.beforeStage && (p.stages.length === 0 || p.stages.includes(stage)));
107
+
108
+ for (const plugin of applicable) {
109
+ try {
110
+ const result = await plugin.beforeStage({ ...context, stage, pluginName: plugin.name });
111
+ if (result && typeof result === 'object') {
112
+ Object.assign(context, result); // Allow plugins to modify context
113
+ }
114
+ audit(AUDIT_EVENTS.PLUGIN_EXECUTED, {
115
+ name: plugin.name,
116
+ hook: 'beforeStage',
117
+ stage,
118
+ });
119
+ } catch (err) {
120
+ audit(AUDIT_EVENTS.PLUGIN_ERROR, {
121
+ name: plugin.name,
122
+ hook: 'beforeStage',
123
+ stage,
124
+ error: err.message,
125
+ });
126
+ }
127
+ }
128
+
129
+ return context;
130
+ }
131
+
132
+ /**
133
+ * Run afterStage hooks for all plugins registered for this stage.
134
+ *
135
+ * @param {string} stage - Pipeline stage name
136
+ * @param {object} context - Stage context + result from stage execution
137
+ * @returns {object} Modified context
138
+ */
139
+ export async function runAfterStage(stage, context = {}) {
140
+ const plugins = await loadPlugins();
141
+ const applicable = plugins.filter(p => p.enabled && p.afterStage && (p.stages.length === 0 || p.stages.includes(stage)));
142
+
143
+ for (const plugin of applicable) {
144
+ try {
145
+ const result = await plugin.afterStage({ ...context, stage, pluginName: plugin.name });
146
+ if (result && typeof result === 'object') {
147
+ Object.assign(context, result);
148
+ }
149
+ audit(AUDIT_EVENTS.PLUGIN_EXECUTED, {
150
+ name: plugin.name,
151
+ hook: 'afterStage',
152
+ stage,
153
+ });
154
+ } catch (err) {
155
+ audit(AUDIT_EVENTS.PLUGIN_ERROR, {
156
+ name: plugin.name,
157
+ hook: 'afterStage',
158
+ stage,
159
+ error: err.message,
160
+ });
161
+ }
162
+ }
163
+
164
+ return context;
165
+ }
166
+
167
+ /**
168
+ * Fire a plugin event — runs all plugins listening for this event.
169
+ *
170
+ * @param {string} eventName - Event name
171
+ * @param {object} data - Event data
172
+ */
173
+ export async function firePluginEvent(eventName, data = {}) {
174
+ const plugins = await loadPlugins();
175
+
176
+ for (const plugin of plugins) {
177
+ if (!plugin.enabled || !plugin.onEvent[eventName]) continue;
178
+
179
+ try {
180
+ await plugin.onEvent[eventName](data);
181
+ } catch (err) {
182
+ audit(AUDIT_EVENTS.PLUGIN_ERROR, {
183
+ name: plugin.name,
184
+ hook: 'onEvent',
185
+ event: eventName,
186
+ error: err.message,
187
+ });
188
+ }
189
+ }
190
+ }
191
+
192
+ /**
193
+ * Get all custom commands registered by plugins.
194
+ *
195
+ * @returns {object} Map of command → { pluginName, handler }
196
+ */
197
+ export async function getPluginCommands() {
198
+ const plugins = await loadPlugins();
199
+ const commands = {};
200
+
201
+ for (const plugin of plugins) {
202
+ if (!plugin.enabled) continue;
203
+ for (const [cmd, handler] of Object.entries(plugin.commands)) {
204
+ commands[cmd] = { pluginName: plugin.name, handler };
205
+ }
206
+ }
207
+
208
+ return commands;
209
+ }
210
+
211
+ /**
212
+ * Get loaded plugin info for status display.
213
+ *
214
+ * @returns {Array} Plugin metadata
215
+ */
216
+ export function getPluginStatus() {
217
+ return _loadedPlugins.map(p => ({
218
+ name: p.name,
219
+ enabled: p.enabled,
220
+ stages: p.stages,
221
+ hasBeforeStage: !!p.beforeStage,
222
+ hasAfterStage: !!p.afterStage,
223
+ commands: Object.keys(p.commands),
224
+ events: Object.keys(p.onEvent),
225
+ }));
226
+ }
227
+
228
+ /**
229
+ * Reset plugin registry (for testing or config reload).
230
+ */
231
+ export function resetPlugins() {
232
+ _loadedPlugins = [];
233
+ _initialized = false;
234
+ }
@@ -0,0 +1,84 @@
1
+ export const DEFAULT_RATE_LIMIT = {
2
+ windowMs: 60_000,
3
+ max: 60,
4
+ keyGenerator: (req) => req.ip,
5
+ skipPaths: ['/api/status', '/metrics'],
6
+ };
7
+
8
+ /** @type {Map<string, { timestamps: number[], blocked: number }>} */
9
+ const clients = new Map();
10
+ let totalBlocked = 0;
11
+ let cleanupTimer = null;
12
+
13
+ function startCleanup(windowMs) {
14
+ if (cleanupTimer) return;
15
+ cleanupTimer = setInterval(() => {
16
+ const now = Date.now();
17
+ for (const [key, entry] of clients) {
18
+ entry.timestamps = entry.timestamps.filter((t) => now - t < windowMs);
19
+ if (entry.timestamps.length === 0) clients.delete(key);
20
+ }
21
+ }, 60_000);
22
+ if (cleanupTimer.unref) cleanupTimer.unref();
23
+ }
24
+
25
+ export function createRateLimiter(options = {}) {
26
+ const opts = { ...DEFAULT_RATE_LIMIT, ...options };
27
+ const { windowMs, max, keyGenerator, skipPaths } = opts;
28
+
29
+ startCleanup(windowMs);
30
+
31
+ return function rateLimitMiddleware(req, res, next) {
32
+ // Skip WebSocket upgrades
33
+ if (req.headers.upgrade === 'websocket') return next();
34
+
35
+ // Skip configured paths
36
+ if (skipPaths.some((p) => req.path === p || req.path.startsWith(p + '/'))) {
37
+ return next();
38
+ }
39
+
40
+ const key = keyGenerator(req);
41
+ const now = Date.now();
42
+
43
+ if (!clients.has(key)) {
44
+ clients.set(key, { timestamps: [], blocked: 0 });
45
+ }
46
+
47
+ const entry = clients.get(key);
48
+ // Sliding window: keep only timestamps within the current window
49
+ entry.timestamps = entry.timestamps.filter((t) => now - t < windowMs);
50
+
51
+ if (entry.timestamps.length >= max) {
52
+ const oldestInWindow = entry.timestamps[0];
53
+ const retryAfter = Math.ceil((oldestInWindow + windowMs - now) / 1000);
54
+
55
+ entry.blocked++;
56
+ totalBlocked++;
57
+
58
+ res.set('Retry-After', String(retryAfter));
59
+ return res.status(429).json({
60
+ error: 'Too many requests',
61
+ retryAfter,
62
+ });
63
+ }
64
+
65
+ entry.timestamps.push(now);
66
+ return next();
67
+ };
68
+ }
69
+
70
+ export function getRateLimitStatus() {
71
+ let activeClients = 0;
72
+ let totalTracked = 0;
73
+ for (const entry of clients.values()) {
74
+ if (entry.timestamps.length > 0) {
75
+ activeClients++;
76
+ totalTracked += entry.timestamps.length;
77
+ }
78
+ }
79
+ return {
80
+ activeClients,
81
+ totalTracked,
82
+ totalBlocked,
83
+ };
84
+ }
@@ -0,0 +1,74 @@
1
+ import { getConfig } from '../config.js';
2
+
3
+ export const ROLES = {
4
+ admin: {
5
+ permissions: [
6
+ 'feature', 'deploy', 'approve', 'reject', 'review', 'implement',
7
+ 'fix', 'reset', 'cleanup', 'config', 'retry', 'dryrun', 'audit',
8
+ 'cost', 'status', 'logs', 'docs', 'health', 'ask'
9
+ ]
10
+ },
11
+ developer: {
12
+ permissions: [
13
+ 'feature', 'review', 'implement', 'fix', 'status', 'logs',
14
+ 'docs', 'cost', 'retry', 'dryrun', 'ask'
15
+ ]
16
+ },
17
+ viewer: {
18
+ permissions: ['status', 'logs', 'docs', 'health', 'cost', 'ask']
19
+ }
20
+ };
21
+
22
+ export const PERMISSIONS = [...new Set(
23
+ Object.values(ROLES).flatMap(r => r.permissions)
24
+ )];
25
+
26
+ function getRBACConfig() {
27
+ try {
28
+ const config = getConfig();
29
+ return config.rbac || null;
30
+ } catch {
31
+ return null;
32
+ }
33
+ }
34
+
35
+ export function isRBACEnabled() {
36
+ const rbac = getRBACConfig();
37
+ return rbac !== null && Object.keys(rbac).length > 0;
38
+ }
39
+
40
+ export function getUserRole(userId) {
41
+ if (!isRBACEnabled()) return 'admin';
42
+ const rbac = getRBACConfig();
43
+ const users = rbac.users || {};
44
+ return users[String(userId)] || rbac.defaultRole || 'viewer';
45
+ }
46
+
47
+ export function hasPermission(userId, action) {
48
+ const role = getUserRole(userId);
49
+ const roleDef = ROLES[role];
50
+ if (!roleDef) return false;
51
+ return roleDef.permissions.includes(action);
52
+ }
53
+
54
+ export function requirePermission(userId, action) {
55
+ if (!hasPermission(userId, action)) {
56
+ const role = getUserRole(userId);
57
+ throw new Error(
58
+ `Permission denied: role '${role}' cannot perform '${action}'`
59
+ );
60
+ }
61
+ return true;
62
+ }
63
+
64
+ export function checkApiKey(apiKey) {
65
+ if (!isRBACEnabled()) return 'admin';
66
+ const rbac = getRBACConfig();
67
+ const apiKeys = rbac.apiKeys || {};
68
+ return apiKeys[apiKey] || null;
69
+ }
70
+
71
+ export function listPermissions(role) {
72
+ const roleDef = ROLES[role];
73
+ return roleDef ? [...roleDef.permissions] : [];
74
+ }