heyio 1.13.0 → 3.0.1
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/README.md +162 -278
- package/dist/api/middleware/auth.d.ts +14 -0
- package/dist/api/middleware/auth.d.ts.map +1 -0
- package/dist/api/middleware/auth.js +66 -0
- package/dist/api/middleware/auth.js.map +1 -0
- package/dist/api/notifications.d.ts +14 -0
- package/dist/api/notifications.d.ts.map +1 -0
- package/dist/api/notifications.js +112 -0
- package/dist/api/notifications.js.map +1 -0
- package/dist/api/routes/activity.d.ts +3 -0
- package/dist/api/routes/activity.d.ts.map +1 -0
- package/dist/api/routes/activity.js +28 -0
- package/dist/api/routes/activity.js.map +1 -0
- package/dist/api/routes/attachments.d.ts +3 -0
- package/dist/api/routes/attachments.d.ts.map +1 -0
- package/dist/api/routes/attachments.js +83 -0
- package/dist/api/routes/attachments.js.map +1 -0
- package/dist/api/routes/config.d.ts +3 -0
- package/dist/api/routes/config.d.ts.map +1 -0
- package/dist/api/routes/config.js +106 -0
- package/dist/api/routes/config.js.map +1 -0
- package/dist/api/routes/conversations.d.ts +3 -0
- package/dist/api/routes/conversations.d.ts.map +1 -0
- package/dist/api/routes/conversations.js +69 -0
- package/dist/api/routes/conversations.js.map +1 -0
- package/dist/api/routes/health.d.ts +3 -0
- package/dist/api/routes/health.d.ts.map +1 -0
- package/dist/api/routes/health.js +16 -0
- package/dist/api/routes/health.js.map +1 -0
- package/dist/api/routes/inbox.d.ts +3 -0
- package/dist/api/routes/inbox.d.ts.map +1 -0
- package/dist/api/routes/inbox.js +88 -0
- package/dist/api/routes/inbox.js.map +1 -0
- package/dist/api/routes/schedules.d.ts +3 -0
- package/dist/api/routes/schedules.d.ts.map +1 -0
- package/dist/api/routes/schedules.js +96 -0
- package/dist/api/routes/schedules.js.map +1 -0
- package/dist/api/routes/skills.d.ts +2 -0
- package/dist/api/routes/skills.d.ts.map +1 -0
- package/dist/api/routes/skills.js +85 -0
- package/dist/api/routes/skills.js.map +1 -0
- package/dist/api/routes/squads.d.ts +3 -0
- package/dist/api/routes/squads.d.ts.map +1 -0
- package/dist/api/routes/squads.js +129 -0
- package/dist/api/routes/squads.js.map +1 -0
- package/dist/api/routes/usage.d.ts +3 -0
- package/dist/api/routes/usage.d.ts.map +1 -0
- package/dist/api/routes/usage.js +55 -0
- package/dist/api/routes/usage.js.map +1 -0
- package/dist/api/routes/wiki.d.ts +2 -0
- package/dist/api/routes/wiki.d.ts.map +1 -0
- package/dist/api/routes/wiki.js +43 -0
- package/dist/api/routes/wiki.js.map +1 -0
- package/dist/api/server.d.ts +7 -0
- package/dist/api/server.d.ts.map +1 -0
- package/dist/api/server.js +136 -634
- package/dist/api/server.js.map +1 -0
- package/dist/config.d.ts +3 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +2 -91
- package/dist/config.js.map +1 -0
- package/dist/copilot/client.d.ts +5 -0
- package/dist/copilot/client.d.ts.map +1 -0
- package/dist/copilot/client.js +19 -11
- package/dist/copilot/client.js.map +1 -0
- package/dist/copilot/health-monitor.d.ts +14 -0
- package/dist/copilot/health-monitor.d.ts.map +1 -0
- package/dist/copilot/health-monitor.js +70 -0
- package/dist/copilot/health-monitor.js.map +1 -0
- package/dist/copilot/orchestrator.d.ts +5 -0
- package/dist/copilot/orchestrator.d.ts.map +1 -0
- package/dist/copilot/orchestrator.js +127 -123
- package/dist/copilot/orchestrator.js.map +1 -0
- package/dist/copilot/tools.d.ts +49 -0
- package/dist/copilot/tools.d.ts.map +1 -0
- package/dist/copilot/tools.js +545 -321
- package/dist/copilot/tools.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +82 -26
- package/dist/index.js.map +1 -0
- package/dist/logging/logger.d.ts +6 -0
- package/dist/logging/logger.d.ts.map +1 -0
- package/dist/logging/logger.js +21 -0
- package/dist/logging/logger.js.map +1 -0
- package/dist/models/index.d.ts +6 -0
- package/dist/models/index.d.ts.map +1 -0
- package/dist/models/index.js +4 -0
- package/dist/models/index.js.map +1 -0
- package/dist/models/pricing.d.ts +25 -0
- package/dist/models/pricing.d.ts.map +1 -0
- package/dist/models/pricing.js +96 -0
- package/dist/models/pricing.js.map +1 -0
- package/dist/models/registry.d.ts +34 -0
- package/dist/models/registry.d.ts.map +1 -0
- package/dist/models/registry.js +109 -0
- package/dist/models/registry.js.map +1 -0
- package/dist/models/token-tracker.d.ts +40 -0
- package/dist/models/token-tracker.d.ts.map +1 -0
- package/dist/models/token-tracker.js +102 -0
- package/dist/models/token-tracker.js.map +1 -0
- package/dist/scheduler/engine.d.ts +9 -0
- package/dist/scheduler/engine.d.ts.map +1 -0
- package/dist/scheduler/engine.js +127 -0
- package/dist/scheduler/engine.js.map +1 -0
- package/dist/skills/index.d.ts +3 -0
- package/dist/skills/index.d.ts.map +1 -0
- package/dist/skills/index.js +2 -0
- package/dist/skills/index.js.map +1 -0
- package/dist/skills/store.d.ts +52 -0
- package/dist/skills/store.d.ts.map +1 -0
- package/dist/skills/store.js +148 -0
- package/dist/skills/store.js.map +1 -0
- package/dist/squad/agent.d.ts +46 -0
- package/dist/squad/agent.d.ts.map +1 -0
- package/dist/squad/agent.js +261 -0
- package/dist/squad/agent.js.map +1 -0
- package/dist/squad/autonomy.d.ts +16 -0
- package/dist/squad/autonomy.d.ts.map +1 -0
- package/dist/squad/autonomy.js +63 -0
- package/dist/squad/autonomy.js.map +1 -0
- package/dist/squad/event-bus.d.ts +22 -0
- package/dist/squad/event-bus.d.ts.map +1 -0
- package/dist/squad/event-bus.js +56 -0
- package/dist/squad/event-bus.js.map +1 -0
- package/dist/squad/execution/index.d.ts +12 -0
- package/dist/squad/execution/index.d.ts.map +1 -0
- package/dist/squad/execution/index.js +7 -0
- package/dist/squad/execution/index.js.map +1 -0
- package/dist/squad/execution/instance.d.ts +40 -0
- package/dist/squad/execution/instance.d.ts.map +1 -0
- package/dist/squad/execution/instance.js +138 -0
- package/dist/squad/execution/instance.js.map +1 -0
- package/dist/squad/execution/meeting.d.ts +25 -0
- package/dist/squad/execution/meeting.d.ts.map +1 -0
- package/dist/squad/execution/meeting.js +140 -0
- package/dist/squad/execution/meeting.js.map +1 -0
- package/dist/squad/execution/pr.d.ts +15 -0
- package/dist/squad/execution/pr.d.ts.map +1 -0
- package/dist/squad/execution/pr.js +93 -0
- package/dist/squad/execution/pr.js.map +1 -0
- package/dist/squad/execution/runner.d.ts +22 -0
- package/dist/squad/execution/runner.d.ts.map +1 -0
- package/dist/squad/execution/runner.js +68 -0
- package/dist/squad/execution/runner.js.map +1 -0
- package/dist/squad/execution/tasks.d.ts +11 -0
- package/dist/squad/execution/tasks.d.ts.map +1 -0
- package/dist/squad/execution/tasks.js +85 -0
- package/dist/squad/execution/tasks.js.map +1 -0
- package/dist/squad/execution/worktree.d.ts +26 -0
- package/dist/squad/execution/worktree.d.ts.map +1 -0
- package/dist/squad/execution/worktree.js +111 -0
- package/dist/squad/execution/worktree.js.map +1 -0
- package/dist/squad/hiring.d.ts +32 -0
- package/dist/squad/hiring.d.ts.map +1 -0
- package/dist/squad/hiring.js +200 -0
- package/dist/squad/hiring.js.map +1 -0
- package/dist/squad/index.d.ts +8 -0
- package/dist/squad/index.d.ts.map +1 -0
- package/dist/squad/index.js +6 -0
- package/dist/squad/index.js.map +1 -0
- package/dist/squad/manager.d.ts +48 -0
- package/dist/squad/manager.d.ts.map +1 -0
- package/dist/squad/manager.js +274 -0
- package/dist/squad/manager.js.map +1 -0
- package/dist/squad/name-generator.d.ts +16 -0
- package/dist/squad/name-generator.d.ts.map +1 -0
- package/dist/squad/name-generator.js +113 -0
- package/dist/squad/name-generator.js.map +1 -0
- package/dist/squad/roles/templates.d.ts +5 -0
- package/dist/squad/roles/templates.d.ts.map +1 -0
- package/dist/squad/roles/templates.js +102 -0
- package/dist/squad/roles/templates.js.map +1 -0
- package/dist/squad/skill-parser.d.ts +36 -0
- package/dist/squad/skill-parser.d.ts.map +1 -0
- package/dist/squad/skill-parser.js +83 -0
- package/dist/squad/skill-parser.js.map +1 -0
- package/dist/squad/source-resolver.d.ts +20 -0
- package/dist/squad/source-resolver.d.ts.map +1 -0
- package/dist/squad/source-resolver.js +52 -0
- package/dist/squad/source-resolver.js.map +1 -0
- package/dist/store/activity.d.ts +43 -0
- package/dist/store/activity.d.ts.map +1 -0
- package/dist/store/activity.js +131 -0
- package/dist/store/activity.js.map +1 -0
- package/dist/store/db.d.ts +5 -0
- package/dist/store/db.d.ts.map +1 -0
- package/dist/store/db.js +209 -248
- package/dist/store/db.js.map +1 -0
- package/dist/store/inbox.d.ts +53 -0
- package/dist/store/inbox.d.ts.map +1 -0
- package/dist/store/inbox.js +151 -0
- package/dist/store/inbox.js.map +1 -0
- package/dist/store/schedules.d.ts +53 -0
- package/dist/store/schedules.d.ts.map +1 -0
- package/dist/store/schedules.js +149 -54
- package/dist/store/schedules.js.map +1 -0
- package/dist/wiki/index.d.ts +3 -0
- package/dist/wiki/index.d.ts.map +1 -0
- package/dist/wiki/index.js +2 -0
- package/dist/wiki/index.js.map +1 -0
- package/dist/wiki/store.d.ts +49 -0
- package/dist/wiki/store.d.ts.map +1 -0
- package/dist/wiki/store.js +115 -0
- package/dist/wiki/store.js.map +1 -0
- package/node_modules/@io/shared/dist/config.d.ts +25 -0
- package/node_modules/@io/shared/dist/config.d.ts.map +1 -0
- package/node_modules/@io/shared/dist/config.js +47 -0
- package/node_modules/@io/shared/dist/config.js.map +1 -0
- package/node_modules/@io/shared/dist/constants.d.ts +13 -0
- package/node_modules/@io/shared/dist/constants.d.ts.map +1 -0
- package/node_modules/@io/shared/dist/constants.js +34 -0
- package/node_modules/@io/shared/dist/constants.js.map +1 -0
- package/node_modules/@io/shared/dist/index.d.ts +11 -0
- package/node_modules/@io/shared/dist/index.d.ts.map +1 -0
- package/node_modules/@io/shared/dist/index.js +3 -0
- package/node_modules/@io/shared/dist/index.js.map +1 -0
- package/node_modules/@io/shared/dist/types/agents.d.ts +3 -0
- package/node_modules/@io/shared/dist/types/agents.d.ts.map +1 -0
- package/node_modules/@io/shared/dist/types/agents.js +2 -0
- package/node_modules/@io/shared/dist/types/agents.js.map +1 -0
- package/node_modules/@io/shared/dist/types/api.d.ts +33 -0
- package/node_modules/@io/shared/dist/types/api.d.ts.map +1 -0
- package/node_modules/@io/shared/dist/types/api.js +2 -0
- package/node_modules/@io/shared/dist/types/api.js.map +1 -0
- package/node_modules/@io/shared/dist/types/attachments.d.ts +10 -0
- package/node_modules/@io/shared/dist/types/attachments.d.ts.map +1 -0
- package/node_modules/@io/shared/dist/types/attachments.js +2 -0
- package/node_modules/@io/shared/dist/types/attachments.js.map +1 -0
- package/node_modules/@io/shared/dist/types/events.d.ts +44 -0
- package/node_modules/@io/shared/dist/types/events.d.ts.map +1 -0
- package/node_modules/@io/shared/dist/types/events.js +2 -0
- package/node_modules/@io/shared/dist/types/events.js.map +1 -0
- package/node_modules/@io/shared/dist/types/messages.d.ts +15 -0
- package/node_modules/@io/shared/dist/types/messages.d.ts.map +1 -0
- package/node_modules/@io/shared/dist/types/messages.js +2 -0
- package/node_modules/@io/shared/dist/types/messages.js.map +1 -0
- package/node_modules/@io/shared/dist/types/squads.d.ts +43 -0
- package/node_modules/@io/shared/dist/types/squads.d.ts.map +1 -0
- package/node_modules/@io/shared/dist/types/squads.js +2 -0
- package/node_modules/@io/shared/dist/types/squads.js.map +1 -0
- package/node_modules/@io/shared/dist/types/tokens.d.ts +19 -0
- package/node_modules/@io/shared/dist/types/tokens.d.ts.map +1 -0
- package/node_modules/@io/shared/dist/types/tokens.js +2 -0
- package/node_modules/@io/shared/dist/types/tokens.js.map +1 -0
- package/node_modules/@io/shared/package.json +18 -0
- package/node_modules/@io/shared/src/config.ts +74 -0
- package/node_modules/@io/shared/src/constants.ts +36 -0
- package/node_modules/@io/shared/src/index.ts +37 -0
- package/node_modules/@io/shared/src/types/agents.ts +3 -0
- package/node_modules/@io/shared/src/types/api.ts +35 -0
- package/node_modules/@io/shared/src/types/attachments.ts +9 -0
- package/node_modules/@io/shared/src/types/events.ts +81 -0
- package/node_modules/@io/shared/src/types/messages.ts +15 -0
- package/node_modules/@io/shared/src/types/squads.ts +53 -0
- package/node_modules/@io/shared/src/types/tokens.ts +19 -0
- package/node_modules/@io/shared/tsconfig.json +9 -0
- package/node_modules/@io/shared/tsconfig.tsbuildinfo +1 -0
- package/package.json +56 -59
- package/src/api/middleware/auth.ts +76 -0
- package/src/api/notifications.ts +122 -0
- package/src/api/routes/activity.ts +29 -0
- package/src/api/routes/attachments.ts +93 -0
- package/src/api/routes/config.ts +115 -0
- package/src/api/routes/conversations.ts +87 -0
- package/src/api/routes/health.ts +18 -0
- package/src/api/routes/inbox.ts +98 -0
- package/src/api/routes/schedules.ts +121 -0
- package/src/api/routes/skills.ts +105 -0
- package/src/api/routes/squads.ts +145 -0
- package/src/api/routes/usage.ts +57 -0
- package/src/api/routes/wiki.ts +49 -0
- package/src/api/server.ts +186 -0
- package/src/config.ts +3 -0
- package/src/copilot/client.ts +42 -0
- package/src/copilot/health-monitor.ts +85 -0
- package/src/copilot/orchestrator.ts +222 -0
- package/src/copilot/tools.ts +707 -0
- package/src/index.ts +112 -0
- package/src/logging/logger.ts +26 -0
- package/src/models/index.ts +11 -0
- package/src/models/pricing.ts +121 -0
- package/src/models/registry.ts +131 -0
- package/src/models/token-tracker.ts +151 -0
- package/src/scheduler/engine.ts +146 -0
- package/src/skills/index.ts +13 -0
- package/src/skills/store.ts +188 -0
- package/src/squad/agent.ts +326 -0
- package/src/squad/autonomy.ts +78 -0
- package/src/squad/event-bus.ts +71 -0
- package/src/squad/execution/index.ts +17 -0
- package/src/squad/execution/instance.ts +186 -0
- package/src/squad/execution/meeting.ts +191 -0
- package/src/squad/execution/pr.ts +127 -0
- package/src/squad/execution/runner.ts +97 -0
- package/src/squad/execution/tasks.ts +111 -0
- package/src/squad/execution/worktree.ts +138 -0
- package/src/squad/hiring.ts +222 -0
- package/src/squad/index.ts +17 -0
- package/src/squad/manager.ts +337 -0
- package/src/squad/name-generator.ts +135 -0
- package/src/squad/roles/templates.ts +104 -0
- package/src/squad/skill-parser.ts +120 -0
- package/src/squad/source-resolver.ts +57 -0
- package/src/store/activity.ts +176 -0
- package/src/store/db.ts +237 -0
- package/src/store/inbox.ts +199 -0
- package/src/store/schedules.ts +199 -0
- package/src/wiki/index.ts +12 -0
- package/src/wiki/store.ts +139 -0
- package/tsconfig.json +9 -0
- package/LICENSE +0 -21
- package/dist/api/auth.js +0 -46
- package/dist/chat/attachments.js +0 -112
- package/dist/copilot/agents.js +0 -309
- package/dist/copilot/ceremonies.js +0 -174
- package/dist/copilot/gh-token.js +0 -64
- package/dist/copilot/io-scheduler.js +0 -79
- package/dist/copilot/model-router.js +0 -114
- package/dist/copilot/scheduler.js +0 -88
- package/dist/copilot/skills.js +0 -252
- package/dist/copilot/specialist-runner.js +0 -191
- package/dist/copilot/squad-tools.js +0 -258
- package/dist/copilot/system-message.js +0 -86
- package/dist/copilot/token-tracker.js +0 -98
- package/dist/copilot/trigger-schedule.js +0 -33
- package/dist/daemon.js +0 -67
- package/dist/logging.js +0 -27
- package/dist/mcp/config.js +0 -29
- package/dist/mcp/index.js +0 -3
- package/dist/mcp/registry.js +0 -42
- package/dist/notify.js +0 -25
- package/dist/paths.js +0 -17
- package/dist/setup.js +0 -35
- package/dist/store/agent-events.js +0 -19
- package/dist/store/audit-log.js +0 -71
- package/dist/store/conversations.js +0 -164
- package/dist/store/feed.js +0 -44
- package/dist/store/instances.js +0 -75
- package/dist/store/squad-colors.js +0 -23
- package/dist/store/squads.js +0 -60
- package/dist/store/tasks.js +0 -78
- package/dist/store/token-usage.js +0 -94
- package/dist/telegram/bot.js +0 -41
- package/dist/telegram/handlers.js +0 -42
- package/dist/watchdog.js +0 -37
- package/dist/wiki/backlinks.js +0 -51
- package/dist/wiki/fs.js +0 -108
- package/dist/wiki/search.js +0 -47
- package/web-dist/assets/AuditLogView-BzfjNXBT.js +0 -6
- package/web-dist/assets/ChatView-BdMukPKG.js +0 -1
- package/web-dist/assets/FeedView-BfPIabGr.js +0 -6
- package/web-dist/assets/HistoryView-BmEEk3Rs.js +0 -1
- package/web-dist/assets/LoginView-D7LrkeX7.js +0 -1
- package/web-dist/assets/McpView-BAP_ah3T.js +0 -1
- package/web-dist/assets/SchedulesView-CAtsUPCZ.js +0 -6
- package/web-dist/assets/SettingsView-BovjWZDa.js +0 -1
- package/web-dist/assets/SkillsView-DQSMM5LN.js +0 -16
- package/web-dist/assets/SquadDetailView-DBscu0m2.js +0 -26
- package/web-dist/assets/SquadHealthView-D686BuQo.js +0 -11
- package/web-dist/assets/SquadsView-AzMht2NJ.js +0 -6
- package/web-dist/assets/ToggleSwitch.vue_vue_type_script_setup_true_lang-DtShZAjW.js +0 -1
- package/web-dist/assets/UsageView-DiVn97aI.js +0 -16
- package/web-dist/assets/WikiView-Cn7KipkZ.js +0 -26
- package/web-dist/assets/api-D4mHJ3u0.js +0 -1
- package/web-dist/assets/arrow-left-D_qUNUWW.js +0 -6
- package/web-dist/assets/git-branch-DOM-orcl.js +0 -6
- package/web-dist/assets/index-Bo83B1LR.css +0 -1
- package/web-dist/assets/index-ELvnkQjd.js +0 -273
- package/web-dist/assets/pencil-CFsi7ufI.js +0 -6
- package/web-dist/assets/plus-BAzlGFd_.js +0 -6
- package/web-dist/assets/save-BmgCYJ1g.js +0 -6
- package/web-dist/assets/search-CS9zSIeW.js +0 -6
- package/web-dist/assets/squad-colors-B8B_Y-lz.js +0 -1
- package/web-dist/assets/trash-2-DLveUEsd.js +0 -6
- package/web-dist/assets/triangle-alert-lj4I30rL.js +0 -6
- package/web-dist/assets/x-CjXR97Fa.js +0 -6
- package/web-dist/favicon.svg +0 -10
- package/web-dist/index.html +0 -14
- package/web-dist/logo.svg +0 -10
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { Router } from 'express';
|
|
5
|
+
import { loadConfig } from '../../config.js';
|
|
6
|
+
|
|
7
|
+
export function configRouter(): Router {
|
|
8
|
+
const router = Router();
|
|
9
|
+
|
|
10
|
+
function getConfigPath(): string {
|
|
11
|
+
const dataDir = process.env.IO_DATA_DIR ?? join(homedir(), '.io');
|
|
12
|
+
const resolved = dataDir.startsWith('~') ? join(homedir(), dataDir.slice(1)) : dataDir;
|
|
13
|
+
return join(resolved, 'config.json');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* GET /api/config
|
|
18
|
+
* Return current config with sensitive fields redacted.
|
|
19
|
+
*/
|
|
20
|
+
router.get('/config', (_req, res) => {
|
|
21
|
+
try {
|
|
22
|
+
const config = loadConfig();
|
|
23
|
+
// Redact sensitive fields
|
|
24
|
+
const redacted = {
|
|
25
|
+
apiPort: config.apiPort,
|
|
26
|
+
logLevel: config.logLevel,
|
|
27
|
+
defaultModel: config.defaultModel,
|
|
28
|
+
maxInstancesPerSquad: config.maxInstancesPerSquad,
|
|
29
|
+
dataDir: config.dataDir,
|
|
30
|
+
pricing: config.pricing,
|
|
31
|
+
telegram: {
|
|
32
|
+
botToken: config.telegram.botToken ? '••••••••' : null,
|
|
33
|
+
allowedChatIds: config.telegram.allowedChatIds,
|
|
34
|
+
},
|
|
35
|
+
supabase: {
|
|
36
|
+
projectUrl: config.supabase.projectUrl,
|
|
37
|
+
anonKey: config.supabase.anonKey,
|
|
38
|
+
jwtSecret: config.supabase.jwtSecret ? '••••••••' : null,
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
res.json({ config: redacted });
|
|
42
|
+
} catch (err) {
|
|
43
|
+
res.status(500).json({ error: 'Failed to load config' });
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* PATCH /api/config
|
|
49
|
+
* Merge partial config into config.json on disk.
|
|
50
|
+
* Does NOT accept dataDir changes (immutable).
|
|
51
|
+
* Body: partial config object
|
|
52
|
+
*/
|
|
53
|
+
router.patch('/config', (req, res) => {
|
|
54
|
+
try {
|
|
55
|
+
const updates = req.body as Record<string, any>;
|
|
56
|
+
|
|
57
|
+
if (!updates || typeof updates !== 'object') {
|
|
58
|
+
res.status(400).json({ error: 'Body must be a JSON object' });
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Disallow changing dataDir (would break running daemon)
|
|
63
|
+
delete updates.dataDir;
|
|
64
|
+
|
|
65
|
+
const configPath = getConfigPath();
|
|
66
|
+
let existing: Record<string, any> = {};
|
|
67
|
+
if (existsSync(configPath)) {
|
|
68
|
+
existing = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Deep merge one level (telegram, pricing)
|
|
72
|
+
const merged = { ...existing };
|
|
73
|
+
for (const [key, value] of Object.entries(updates)) {
|
|
74
|
+
if (
|
|
75
|
+
value !== null &&
|
|
76
|
+
typeof value === 'object' &&
|
|
77
|
+
!Array.isArray(value) &&
|
|
78
|
+
typeof merged[key] === 'object'
|
|
79
|
+
) {
|
|
80
|
+
merged[key] = { ...merged[key], ...value };
|
|
81
|
+
} else {
|
|
82
|
+
merged[key] = value;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
writeFileSync(configPath, JSON.stringify(merged, null, 2), 'utf-8');
|
|
87
|
+
|
|
88
|
+
// Reload and return redacted
|
|
89
|
+
const config = loadConfig();
|
|
90
|
+
res.json({
|
|
91
|
+
config: {
|
|
92
|
+
apiPort: config.apiPort,
|
|
93
|
+
logLevel: config.logLevel,
|
|
94
|
+
defaultModel: config.defaultModel,
|
|
95
|
+
maxInstancesPerSquad: config.maxInstancesPerSquad,
|
|
96
|
+
dataDir: config.dataDir,
|
|
97
|
+
pricing: config.pricing,
|
|
98
|
+
telegram: {
|
|
99
|
+
botToken: config.telegram.botToken ? '••••••••' : null,
|
|
100
|
+
allowedChatIds: config.telegram.allowedChatIds,
|
|
101
|
+
},
|
|
102
|
+
supabase: {
|
|
103
|
+
projectUrl: config.supabase.projectUrl,
|
|
104
|
+
anonKey: config.supabase.anonKey,
|
|
105
|
+
jwtSecret: config.supabase.jwtSecret ? '••••••••' : null,
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
} catch (err) {
|
|
110
|
+
res.status(500).json({ error: 'Failed to update config' });
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
return router;
|
|
115
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import { getDatabase } from '../../store/db.js';
|
|
3
|
+
|
|
4
|
+
export function conversationsRouter(): Router {
|
|
5
|
+
const router = Router();
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* GET /api/conversations
|
|
9
|
+
* Load chat history with cursor-based pagination.
|
|
10
|
+
* Query params: limit (default 50), before (message id for pagination)
|
|
11
|
+
*/
|
|
12
|
+
router.get('/conversations', async (req, res) => {
|
|
13
|
+
try {
|
|
14
|
+
const limit = Math.min(Number.parseInt(req.query.limit as string, 10) || 50, 200);
|
|
15
|
+
const before = req.query.before as string | undefined;
|
|
16
|
+
|
|
17
|
+
const db = getDatabase();
|
|
18
|
+
|
|
19
|
+
let rows: Array<{
|
|
20
|
+
id: string;
|
|
21
|
+
role: string;
|
|
22
|
+
content: string;
|
|
23
|
+
source: string | null;
|
|
24
|
+
attachments: string | null;
|
|
25
|
+
created_at: string;
|
|
26
|
+
}>;
|
|
27
|
+
|
|
28
|
+
if (before) {
|
|
29
|
+
// Get the timestamp of the cursor message
|
|
30
|
+
const cursorResult = await db.execute({
|
|
31
|
+
sql: 'SELECT created_at FROM conversations WHERE id = ?',
|
|
32
|
+
args: [before],
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
if (cursorResult.rows.length === 0) {
|
|
36
|
+
res.status(400).json({ error: 'Invalid cursor: message not found' });
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const cursorTime = cursorResult.rows[0].created_at as string;
|
|
41
|
+
|
|
42
|
+
const result = await db.execute({
|
|
43
|
+
sql: `SELECT id, role, content, source, attachments, created_at
|
|
44
|
+
FROM conversations
|
|
45
|
+
WHERE created_at < ? OR (created_at = ? AND id < ?)
|
|
46
|
+
ORDER BY created_at DESC, id DESC
|
|
47
|
+
LIMIT ?`,
|
|
48
|
+
args: [cursorTime, cursorTime, before, limit],
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
rows = result.rows as any;
|
|
52
|
+
} else {
|
|
53
|
+
const result = await db.execute({
|
|
54
|
+
sql: `SELECT id, role, content, source, attachments, created_at
|
|
55
|
+
FROM conversations
|
|
56
|
+
ORDER BY created_at DESC, id DESC
|
|
57
|
+
LIMIT ?`,
|
|
58
|
+
args: [limit],
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
rows = result.rows as any;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Reverse so messages are in chronological order
|
|
65
|
+
const messages = rows.reverse().map((row) => ({
|
|
66
|
+
id: row.id,
|
|
67
|
+
role: row.role,
|
|
68
|
+
content: row.content,
|
|
69
|
+
source: row.source,
|
|
70
|
+
attachments: row.attachments ? JSON.parse(row.attachments) : null,
|
|
71
|
+
timestamp: row.created_at,
|
|
72
|
+
}));
|
|
73
|
+
|
|
74
|
+
const cursor = rows.length > 0 ? rows[0].id : null;
|
|
75
|
+
|
|
76
|
+
res.json({
|
|
77
|
+
messages,
|
|
78
|
+
cursor,
|
|
79
|
+
hasMore: rows.length === limit,
|
|
80
|
+
});
|
|
81
|
+
} catch (err) {
|
|
82
|
+
res.status(500).json({ error: 'Failed to load conversations' });
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
return router;
|
|
87
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import { getHealthStatus } from '../../copilot/health-monitor.js';
|
|
3
|
+
|
|
4
|
+
export function healthRouter(): Router {
|
|
5
|
+
const router = Router();
|
|
6
|
+
|
|
7
|
+
router.get('/health', (_req, res) => {
|
|
8
|
+
const health = getHealthStatus();
|
|
9
|
+
res.json({
|
|
10
|
+
status: health.status,
|
|
11
|
+
uptime: health.uptime,
|
|
12
|
+
copilotConnected: health.copilotConnected,
|
|
13
|
+
lastCheck: health.lastCheck.toISOString(),
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
return router;
|
|
18
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import {
|
|
3
|
+
type InboxKind,
|
|
4
|
+
type InboxStatus,
|
|
5
|
+
getInboxEntry,
|
|
6
|
+
getUnreadCount,
|
|
7
|
+
listInboxEntries,
|
|
8
|
+
markInboxRead,
|
|
9
|
+
resolveInboxEntry,
|
|
10
|
+
} from '../../store/inbox.js';
|
|
11
|
+
|
|
12
|
+
export function inboxRouter(): Router {
|
|
13
|
+
const router = Router();
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* GET /api/inbox
|
|
17
|
+
* List inbox entries with optional filters.
|
|
18
|
+
* Query params: status, squad, kind, limit
|
|
19
|
+
*/
|
|
20
|
+
router.get('/inbox', async (req, res) => {
|
|
21
|
+
try {
|
|
22
|
+
const entries = await listInboxEntries({
|
|
23
|
+
status: req.query.status as InboxStatus | undefined,
|
|
24
|
+
squadId: req.query.squad as string | undefined,
|
|
25
|
+
kind: req.query.kind as InboxKind | undefined,
|
|
26
|
+
limit: req.query.limit ? Number.parseInt(req.query.limit as string, 10) : undefined,
|
|
27
|
+
});
|
|
28
|
+
res.json({ entries });
|
|
29
|
+
} catch {
|
|
30
|
+
res.status(500).json({ error: 'Failed to list inbox entries' });
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* GET /api/inbox/unread-count
|
|
36
|
+
* Get the number of unread inbox entries.
|
|
37
|
+
*/
|
|
38
|
+
router.get('/inbox/unread-count', async (_req, res) => {
|
|
39
|
+
try {
|
|
40
|
+
const count = await getUnreadCount();
|
|
41
|
+
res.json({ count });
|
|
42
|
+
} catch {
|
|
43
|
+
res.status(500).json({ error: 'Failed to get unread count' });
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* GET /api/inbox/:id
|
|
49
|
+
* Get a single inbox entry.
|
|
50
|
+
*/
|
|
51
|
+
router.get('/inbox/:id', async (req, res) => {
|
|
52
|
+
try {
|
|
53
|
+
const entry = await getInboxEntry(req.params.id);
|
|
54
|
+
if (!entry) {
|
|
55
|
+
res.status(404).json({ error: 'Entry not found' });
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
res.json({ entry });
|
|
59
|
+
} catch {
|
|
60
|
+
res.status(500).json({ error: 'Failed to get inbox entry' });
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* POST /api/inbox/:id/read
|
|
66
|
+
* Mark an entry as read.
|
|
67
|
+
*/
|
|
68
|
+
router.post('/inbox/:id/read', async (req, res) => {
|
|
69
|
+
try {
|
|
70
|
+
await markInboxRead(req.params.id);
|
|
71
|
+
res.json({ status: 'ok' });
|
|
72
|
+
} catch {
|
|
73
|
+
res.status(500).json({ error: 'Failed to mark entry as read' });
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* POST /api/inbox/:id/respond
|
|
79
|
+
* Respond to an inbox question. Resolves any blocking squad.
|
|
80
|
+
* Body: { response: string }
|
|
81
|
+
*/
|
|
82
|
+
router.post('/inbox/:id/respond', async (req, res) => {
|
|
83
|
+
try {
|
|
84
|
+
const { response } = req.body as { response?: string };
|
|
85
|
+
if (!response) {
|
|
86
|
+
res.status(400).json({ error: 'response is required' });
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const unblocked = await resolveInboxEntry(req.params.id, response);
|
|
91
|
+
res.json({ status: 'ok', squadUnblocked: unblocked });
|
|
92
|
+
} catch {
|
|
93
|
+
res.status(500).json({ error: 'Failed to respond to entry' });
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
return router;
|
|
98
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import {
|
|
3
|
+
type ScheduleTargetType,
|
|
4
|
+
createSchedule,
|
|
5
|
+
deleteSchedule,
|
|
6
|
+
getSchedule,
|
|
7
|
+
listSchedules,
|
|
8
|
+
updateSchedule,
|
|
9
|
+
} from '../../store/schedules.js';
|
|
10
|
+
|
|
11
|
+
export function schedulesRouter(): Router {
|
|
12
|
+
const router = Router();
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* GET /api/schedules
|
|
16
|
+
* List all schedules. Query param: enabled=true to filter.
|
|
17
|
+
*/
|
|
18
|
+
router.get('/schedules', async (req, res) => {
|
|
19
|
+
try {
|
|
20
|
+
const enabledOnly = req.query.enabled === 'true';
|
|
21
|
+
const schedules = await listSchedules(enabledOnly || undefined);
|
|
22
|
+
res.json({ schedules });
|
|
23
|
+
} catch {
|
|
24
|
+
res.status(500).json({ error: 'Failed to list schedules' });
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* GET /api/schedules/:id
|
|
30
|
+
* Get a single schedule.
|
|
31
|
+
*/
|
|
32
|
+
router.get('/schedules/:id', async (req, res) => {
|
|
33
|
+
try {
|
|
34
|
+
const schedule = await getSchedule(req.params.id);
|
|
35
|
+
if (!schedule) {
|
|
36
|
+
res.status(404).json({ error: 'Schedule not found' });
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
res.json({ schedule });
|
|
40
|
+
} catch {
|
|
41
|
+
res.status(500).json({ error: 'Failed to get schedule' });
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* POST /api/schedules
|
|
47
|
+
* Create a new schedule.
|
|
48
|
+
* Body: { name, targetType, targetId?, cron, prompt, enabled? }
|
|
49
|
+
*/
|
|
50
|
+
router.post('/schedules', async (req, res) => {
|
|
51
|
+
try {
|
|
52
|
+
const { name, targetType, targetId, cron, prompt, enabled } = req.body as {
|
|
53
|
+
name?: string;
|
|
54
|
+
targetType?: ScheduleTargetType;
|
|
55
|
+
targetId?: string;
|
|
56
|
+
cron?: string;
|
|
57
|
+
prompt?: string;
|
|
58
|
+
enabled?: boolean;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
if (!name || !targetType || !cron || !prompt) {
|
|
62
|
+
res.status(400).json({ error: 'name, targetType, cron, and prompt are required' });
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (targetType !== 'squad' && targetType !== 'orchestrator') {
|
|
67
|
+
res.status(400).json({ error: 'targetType must be "squad" or "orchestrator"' });
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const schedule = await createSchedule({ name, targetType, targetId, cron, prompt, enabled });
|
|
72
|
+
res.status(201).json({ schedule });
|
|
73
|
+
} catch (err) {
|
|
74
|
+
const msg = err instanceof Error ? err.message : 'Failed to create schedule';
|
|
75
|
+
res.status(400).json({ error: msg });
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* PATCH /api/schedules/:id
|
|
81
|
+
* Update a schedule (partial). Body: { name?, cron?, prompt?, enabled? }
|
|
82
|
+
*/
|
|
83
|
+
router.patch('/schedules/:id', async (req, res) => {
|
|
84
|
+
try {
|
|
85
|
+
const existing = await getSchedule(req.params.id);
|
|
86
|
+
if (!existing) {
|
|
87
|
+
res.status(404).json({ error: 'Schedule not found' });
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const { name, cron, prompt, enabled } = req.body as {
|
|
92
|
+
name?: string;
|
|
93
|
+
cron?: string;
|
|
94
|
+
prompt?: string;
|
|
95
|
+
enabled?: boolean;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
await updateSchedule(req.params.id, { name, cron, prompt, enabled });
|
|
99
|
+
const updated = await getSchedule(req.params.id);
|
|
100
|
+
res.json({ schedule: updated });
|
|
101
|
+
} catch (err) {
|
|
102
|
+
const msg = err instanceof Error ? err.message : 'Failed to update schedule';
|
|
103
|
+
res.status(400).json({ error: msg });
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* DELETE /api/schedules/:id
|
|
109
|
+
* Delete a schedule.
|
|
110
|
+
*/
|
|
111
|
+
router.delete('/schedules/:id', async (req, res) => {
|
|
112
|
+
try {
|
|
113
|
+
await deleteSchedule(req.params.id);
|
|
114
|
+
res.json({ status: 'ok' });
|
|
115
|
+
} catch {
|
|
116
|
+
res.status(500).json({ error: 'Failed to delete schedule' });
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
return router;
|
|
121
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import {
|
|
3
|
+
activateSkill,
|
|
4
|
+
deactivateSkill,
|
|
5
|
+
getActiveSkills,
|
|
6
|
+
getSkill,
|
|
7
|
+
installSkill,
|
|
8
|
+
installSkillFromUrl,
|
|
9
|
+
listInstalledSkills,
|
|
10
|
+
removeSkill,
|
|
11
|
+
} from '../../skills/index.js';
|
|
12
|
+
|
|
13
|
+
export const skillsRouter = Router();
|
|
14
|
+
|
|
15
|
+
// List installed skills
|
|
16
|
+
skillsRouter.get('/skills', async (_req, res) => {
|
|
17
|
+
const skills = listInstalledSkills();
|
|
18
|
+
const orchestratorActivations = await getActiveSkills('orchestrator');
|
|
19
|
+
|
|
20
|
+
const result = skills.map((s) => ({
|
|
21
|
+
name: s.name,
|
|
22
|
+
activatedForOrchestrator: orchestratorActivations.some((a) => a.skillName === s.name),
|
|
23
|
+
preview: s.content.slice(0, 200),
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
res.json({ skills: result });
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// Get a specific skill
|
|
30
|
+
skillsRouter.get('/skills/:name', (req, res) => {
|
|
31
|
+
const skill = getSkill(req.params.name);
|
|
32
|
+
if (!skill) {
|
|
33
|
+
res.status(404).json({ error: `Skill '${req.params.name}' not found` });
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
res.json(skill);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Install a skill (from URL or content)
|
|
40
|
+
skillsRouter.post('/skills/install', async (req, res) => {
|
|
41
|
+
const { name, url, content } = req.body;
|
|
42
|
+
|
|
43
|
+
if (!name) {
|
|
44
|
+
res.status(400).json({ error: 'name is required' });
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
if (url) {
|
|
50
|
+
const skill = await installSkillFromUrl(name, url);
|
|
51
|
+
res.status(201).json({ installed: true, name: skill.name });
|
|
52
|
+
} else if (content) {
|
|
53
|
+
const skill = installSkill(name, content);
|
|
54
|
+
res.status(201).json({ installed: true, name: skill.name });
|
|
55
|
+
} else {
|
|
56
|
+
res.status(400).json({ error: 'Either "url" or "content" is required' });
|
|
57
|
+
}
|
|
58
|
+
} catch (err) {
|
|
59
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Activate a skill
|
|
64
|
+
skillsRouter.post('/skills/:name/activate', async (req, res) => {
|
|
65
|
+
const { targetType, targetId } = req.body;
|
|
66
|
+
if (!targetType || !['orchestrator', 'squad'].includes(targetType)) {
|
|
67
|
+
res.status(400).json({ error: 'targetType must be "orchestrator" or "squad"' });
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const skill = getSkill(req.params.name);
|
|
72
|
+
if (!skill) {
|
|
73
|
+
res.status(404).json({ error: `Skill '${req.params.name}' not found` });
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
await activateSkill(req.params.name, targetType, targetId);
|
|
78
|
+
res.json({ activated: true, skillName: req.params.name, targetType, targetId });
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// Deactivate a skill
|
|
82
|
+
skillsRouter.post('/skills/:name/deactivate', async (req, res) => {
|
|
83
|
+
const { targetType, targetId } = req.body;
|
|
84
|
+
if (!targetType || !['orchestrator', 'squad'].includes(targetType)) {
|
|
85
|
+
res.status(400).json({ error: 'targetType must be "orchestrator" or "squad"' });
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
await deactivateSkill(req.params.name, targetType, targetId);
|
|
90
|
+
res.json({ deactivated: true, skillName: req.params.name });
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// Remove a skill
|
|
94
|
+
skillsRouter.delete('/skills/:name', (req, res) => {
|
|
95
|
+
removeSkill(req.params.name);
|
|
96
|
+
res.status(204).end();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Get activations for a target
|
|
100
|
+
skillsRouter.get('/skills-activations', async (req, res) => {
|
|
101
|
+
const targetType = (req.query.targetType as string) ?? 'orchestrator';
|
|
102
|
+
const targetId = req.query.targetId as string | undefined;
|
|
103
|
+
const activations = await getActiveSkills(targetType as 'orchestrator' | 'squad', targetId);
|
|
104
|
+
res.json({ activations });
|
|
105
|
+
});
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import { getSquadInstances } from '../../squad/execution/instance.js';
|
|
3
|
+
import { runInstance } from '../../squad/execution/runner.js';
|
|
4
|
+
import { getSquadByName, getSquadMembers, listSquads } from '../../squad/manager.js';
|
|
5
|
+
|
|
6
|
+
export function squadsRouter(): Router {
|
|
7
|
+
const router = Router();
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* GET /api/squads
|
|
11
|
+
* List all active squads.
|
|
12
|
+
*/
|
|
13
|
+
router.get('/squads', async (_req, res) => {
|
|
14
|
+
try {
|
|
15
|
+
const squads = await listSquads();
|
|
16
|
+
const results = await Promise.all(
|
|
17
|
+
squads.map(async (s) => {
|
|
18
|
+
const members = await getSquadMembers(s.id);
|
|
19
|
+
const instances = getSquadInstances(s.id);
|
|
20
|
+
return {
|
|
21
|
+
id: s.id,
|
|
22
|
+
name: s.name,
|
|
23
|
+
projectPath: s.projectPath,
|
|
24
|
+
repoUrl: s.repoUrl,
|
|
25
|
+
universe: s.universe,
|
|
26
|
+
autonomyTier: s.autonomyTier,
|
|
27
|
+
status: s.status,
|
|
28
|
+
memberCount: members.length,
|
|
29
|
+
activeInstances: instances.filter(
|
|
30
|
+
(i) => i.status !== 'complete' && i.status !== 'failed',
|
|
31
|
+
).length,
|
|
32
|
+
createdAt: s.createdAt.toISOString(),
|
|
33
|
+
};
|
|
34
|
+
}),
|
|
35
|
+
);
|
|
36
|
+
res.json({ squads: results });
|
|
37
|
+
} catch (err) {
|
|
38
|
+
res.status(500).json({ error: 'Failed to list squads' });
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* GET /api/squads/:name
|
|
44
|
+
* Get detailed squad info.
|
|
45
|
+
*/
|
|
46
|
+
router.get('/squads/:name', async (req, res) => {
|
|
47
|
+
try {
|
|
48
|
+
const squad = await getSquadByName(req.params.name);
|
|
49
|
+
if (!squad) {
|
|
50
|
+
res.status(404).json({ error: `Squad '${req.params.name}' not found` });
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const members = await getSquadMembers(squad.id);
|
|
55
|
+
const instances = getSquadInstances(squad.id);
|
|
56
|
+
|
|
57
|
+
// Build a map of role -> current task (from active instances)
|
|
58
|
+
const currentTasks = new Map<string, string>();
|
|
59
|
+
for (const inst of instances) {
|
|
60
|
+
if (inst.status !== 'complete' && inst.status !== 'failed') {
|
|
61
|
+
for (const task of inst.tasks) {
|
|
62
|
+
if (task.status === 'in_progress') {
|
|
63
|
+
currentTasks.set(task.assignedTo, task.description);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
res.json({
|
|
70
|
+
squad: {
|
|
71
|
+
id: squad.id,
|
|
72
|
+
name: squad.name,
|
|
73
|
+
projectPath: squad.projectPath,
|
|
74
|
+
repoUrl: squad.repoUrl,
|
|
75
|
+
universe: squad.universe,
|
|
76
|
+
autonomyTier: squad.autonomyTier,
|
|
77
|
+
autonomyConfig: squad.autonomyConfig,
|
|
78
|
+
status: squad.status,
|
|
79
|
+
createdAt: squad.createdAt.toISOString(),
|
|
80
|
+
},
|
|
81
|
+
members: members.map((m) => ({
|
|
82
|
+
id: m.id,
|
|
83
|
+
displayName: m.displayName,
|
|
84
|
+
role: m.roleName,
|
|
85
|
+
persona: m.persona,
|
|
86
|
+
veto: m.isVetoMember,
|
|
87
|
+
tools: m.toolsAllowed,
|
|
88
|
+
status: m.status,
|
|
89
|
+
currentTask: currentTasks.get(m.roleName) ?? null,
|
|
90
|
+
})),
|
|
91
|
+
instances: instances.map((i) => ({
|
|
92
|
+
id: i.id,
|
|
93
|
+
status: i.status,
|
|
94
|
+
issueRef: i.issueRef,
|
|
95
|
+
branch: i.branch,
|
|
96
|
+
taskCount: i.tasks.length,
|
|
97
|
+
tasksComplete: i.tasks.filter((t) => t.status === 'done').length,
|
|
98
|
+
})),
|
|
99
|
+
});
|
|
100
|
+
} catch (err) {
|
|
101
|
+
res.status(500).json({ error: 'Failed to get squad' });
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* POST /api/squads/:name/run
|
|
107
|
+
* Spawn a new instance for a squad.
|
|
108
|
+
* Body: { objective: string, issueRef?: string }
|
|
109
|
+
*/
|
|
110
|
+
router.post('/squads/:name/run', async (req, res) => {
|
|
111
|
+
try {
|
|
112
|
+
const squad = await getSquadByName(req.params.name);
|
|
113
|
+
if (!squad) {
|
|
114
|
+
res.status(404).json({ error: `Squad '${req.params.name}' not found` });
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const { objective, issueRef } = req.body as {
|
|
119
|
+
objective?: string;
|
|
120
|
+
issueRef?: string;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
if (!objective) {
|
|
124
|
+
res.status(400).json({ error: 'objective is required' });
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Run asynchronously — return immediately with instance ID
|
|
129
|
+
const instancePromise = runInstance({ squad, objective, issueRef });
|
|
130
|
+
|
|
131
|
+
// We don't await — return accepted status
|
|
132
|
+
// The client can poll /api/squads/:name for progress
|
|
133
|
+
instancePromise.catch(() => {}); // prevent unhandled rejection
|
|
134
|
+
|
|
135
|
+
res.status(202).json({
|
|
136
|
+
message: `Instance starting for squad '${squad.name}'`,
|
|
137
|
+
objective,
|
|
138
|
+
});
|
|
139
|
+
} catch (err) {
|
|
140
|
+
res.status(500).json({ error: 'Failed to start instance' });
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
return router;
|
|
145
|
+
}
|