@markus-global/cli 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/dist/api-client.d.ts +31 -0
  2. package/dist/api-client.d.ts.map +1 -0
  3. package/dist/api-client.js +96 -0
  4. package/dist/api-client.js.map +1 -0
  5. package/dist/commands/agent.d.ts +3 -0
  6. package/dist/commands/agent.d.ts.map +1 -0
  7. package/dist/commands/agent.js +224 -0
  8. package/dist/commands/agent.js.map +1 -0
  9. package/dist/commands/approval.d.ts +3 -0
  10. package/dist/commands/approval.d.ts.map +1 -0
  11. package/dist/commands/approval.js +59 -0
  12. package/dist/commands/approval.js.map +1 -0
  13. package/dist/commands/audit.d.ts +3 -0
  14. package/dist/commands/audit.d.ts.map +1 -0
  15. package/dist/commands/audit.js +92 -0
  16. package/dist/commands/audit.js.map +1 -0
  17. package/dist/commands/builder.d.ts +3 -0
  18. package/dist/commands/builder.d.ts.map +1 -0
  19. package/dist/commands/builder.js +85 -0
  20. package/dist/commands/builder.js.map +1 -0
  21. package/dist/commands/deliverable.d.ts +3 -0
  22. package/dist/commands/deliverable.d.ts.map +1 -0
  23. package/dist/commands/deliverable.js +127 -0
  24. package/dist/commands/deliverable.js.map +1 -0
  25. package/dist/commands/external-agent.d.ts +3 -0
  26. package/dist/commands/external-agent.d.ts.map +1 -0
  27. package/dist/commands/external-agent.js +74 -0
  28. package/dist/commands/external-agent.js.map +1 -0
  29. package/dist/commands/gateway.d.ts +3 -0
  30. package/dist/commands/gateway.d.ts.map +1 -0
  31. package/dist/commands/gateway.js +155 -0
  32. package/dist/commands/gateway.js.map +1 -0
  33. package/dist/commands/init.d.ts +6 -0
  34. package/dist/commands/init.d.ts.map +1 -0
  35. package/dist/commands/init.js +251 -0
  36. package/dist/commands/init.js.map +1 -0
  37. package/dist/commands/key.d.ts +3 -0
  38. package/dist/commands/key.d.ts.map +1 -0
  39. package/dist/commands/key.js +68 -0
  40. package/dist/commands/key.js.map +1 -0
  41. package/dist/commands/project.d.ts +3 -0
  42. package/dist/commands/project.d.ts.map +1 -0
  43. package/dist/commands/project.js +164 -0
  44. package/dist/commands/project.js.map +1 -0
  45. package/dist/commands/report.d.ts +3 -0
  46. package/dist/commands/report.d.ts.map +1 -0
  47. package/dist/commands/report.js +69 -0
  48. package/dist/commands/report.js.map +1 -0
  49. package/dist/commands/requirement.d.ts +3 -0
  50. package/dist/commands/requirement.d.ts.map +1 -0
  51. package/dist/commands/requirement.js +197 -0
  52. package/dist/commands/requirement.js.map +1 -0
  53. package/dist/commands/review.d.ts +3 -0
  54. package/dist/commands/review.d.ts.map +1 -0
  55. package/dist/commands/review.js +71 -0
  56. package/dist/commands/review.js.map +1 -0
  57. package/dist/commands/role.d.ts +3 -0
  58. package/dist/commands/role.d.ts.map +1 -0
  59. package/dist/commands/role.js +39 -0
  60. package/dist/commands/role.js.map +1 -0
  61. package/dist/commands/settings.d.ts +3 -0
  62. package/dist/commands/settings.d.ts.map +1 -0
  63. package/dist/commands/settings.js +53 -0
  64. package/dist/commands/settings.js.map +1 -0
  65. package/dist/commands/skill.d.ts +3 -0
  66. package/dist/commands/skill.d.ts.map +1 -0
  67. package/dist/commands/skill.js +136 -0
  68. package/dist/commands/skill.js.map +1 -0
  69. package/dist/commands/start.d.ts +3 -0
  70. package/dist/commands/start.d.ts.map +1 -0
  71. package/dist/commands/start.js +628 -0
  72. package/dist/commands/start.js.map +1 -0
  73. package/dist/commands/system.d.ts +3 -0
  74. package/dist/commands/system.d.ts.map +1 -0
  75. package/dist/commands/system.js +248 -0
  76. package/dist/commands/system.js.map +1 -0
  77. package/dist/commands/task.d.ts +3 -0
  78. package/dist/commands/task.d.ts.map +1 -0
  79. package/dist/commands/task.js +510 -0
  80. package/dist/commands/task.js.map +1 -0
  81. package/dist/commands/team.d.ts +3 -0
  82. package/dist/commands/team.d.ts.map +1 -0
  83. package/dist/commands/team.js +312 -0
  84. package/dist/commands/team.js.map +1 -0
  85. package/dist/commands/template.d.ts +3 -0
  86. package/dist/commands/template.d.ts.map +1 -0
  87. package/dist/commands/template.js +77 -0
  88. package/dist/commands/template.js.map +1 -0
  89. package/dist/commands/user.d.ts +3 -0
  90. package/dist/commands/user.d.ts.map +1 -0
  91. package/dist/commands/user.js +68 -0
  92. package/dist/commands/user.js.map +1 -0
  93. package/dist/index.d.ts +3 -0
  94. package/dist/index.d.ts.map +1 -0
  95. package/dist/index.js +96 -0
  96. package/dist/index.js.map +1 -0
  97. package/dist/markus.mjs +82359 -0
  98. package/dist/output.d.ts +31 -0
  99. package/dist/output.d.ts.map +1 -0
  100. package/dist/output.js +121 -0
  101. package/dist/output.js.map +1 -0
  102. package/dist/paths.d.ts +20 -0
  103. package/dist/paths.d.ts.map +1 -0
  104. package/dist/paths.js +61 -0
  105. package/dist/paths.js.map +1 -0
  106. package/dist/web-ui/assets/index-Bcc58A3R.css +1 -0
  107. package/dist/web-ui/assets/index-CP5PJ1Oz.js +61 -0
  108. package/dist/web-ui/index.html +20 -0
  109. package/dist/web-ui/logo.png +0 -0
  110. package/package.json +37 -0
  111. package/templates/openclaw-markus-skill/AGENTS.md +163 -0
  112. package/templates/openclaw-markus-skill/TOOLS.md +155 -0
  113. package/templates/openclaw-markus-skill/config.json5 +44 -0
  114. package/templates/openclaw-markus-skill/heartbeat.md +45 -0
  115. package/templates/roles/SHARED.md +383 -0
  116. package/templates/roles/agent-father/ROLE.md +42 -0
  117. package/templates/roles/content-writer/ROLE.md +28 -0
  118. package/templates/roles/developer/HEARTBEAT.md +8 -0
  119. package/templates/roles/developer/POLICIES.md +31 -0
  120. package/templates/roles/developer/ROLE.md +23 -0
  121. package/templates/roles/devops/ROLE.md +28 -0
  122. package/templates/roles/finance/ROLE.md +16 -0
  123. package/templates/roles/hr/ROLE.md +17 -0
  124. package/templates/roles/marketing/ROLE.md +16 -0
  125. package/templates/roles/openclaw-developer/openclaw.md +78 -0
  126. package/templates/roles/operations/HEARTBEAT.md +10 -0
  127. package/templates/roles/operations/ROLE.md +23 -0
  128. package/templates/roles/org-manager/HEARTBEAT.md +12 -0
  129. package/templates/roles/org-manager/POLICIES.md +14 -0
  130. package/templates/roles/org-manager/ROLE.md +102 -0
  131. package/templates/roles/product-manager/HEARTBEAT.md +9 -0
  132. package/templates/roles/product-manager/ROLE.md +37 -0
  133. package/templates/roles/project-manager/ROLE.md +44 -0
  134. package/templates/roles/qa-engineer/ROLE.md +28 -0
  135. package/templates/roles/research-assistant/ROLE.md +23 -0
  136. package/templates/roles/reviewer/HEARTBEAT.md +13 -0
  137. package/templates/roles/reviewer/ROLE.md +69 -0
  138. package/templates/roles/secretary/HEARTBEAT.md +50 -0
  139. package/templates/roles/secretary/ROLE.md +148 -0
  140. package/templates/roles/skill-architect/ROLE.md +42 -0
  141. package/templates/roles/support/ROLE.md +16 -0
  142. package/templates/roles/team-factory/ROLE.md +54 -0
  143. package/templates/roles/tech-writer/ROLE.md +23 -0
  144. package/templates/skills/agent-building/SKILL.md +140 -0
  145. package/templates/skills/agent-building/skill.json +13 -0
  146. package/templates/skills/chrome-devtools/SKILL.md +257 -0
  147. package/templates/skills/chrome-devtools/skill.json +21 -0
  148. package/templates/skills/markus-admin-cli/SKILL.md +121 -0
  149. package/templates/skills/markus-admin-cli/skill.json +14 -0
  150. package/templates/skills/markus-agent-cli/SKILL.md +67 -0
  151. package/templates/skills/markus-agent-cli/skill.json +14 -0
  152. package/templates/skills/markus-cli/SKILL.md +113 -0
  153. package/templates/skills/markus-cli/skill.json +14 -0
  154. package/templates/skills/markus-hub-connector/SKILL.md +64 -0
  155. package/templates/skills/markus-hub-connector/server.mjs +321 -0
  156. package/templates/skills/markus-hub-connector/skill.json +20 -0
  157. package/templates/skills/markus-project-cli/SKILL.md +161 -0
  158. package/templates/skills/markus-project-cli/skill.json +14 -0
  159. package/templates/skills/markus-skill-cli/SKILL.md +57 -0
  160. package/templates/skills/markus-skill-cli/skill.json +14 -0
  161. package/templates/skills/markus-team-cli/SKILL.md +52 -0
  162. package/templates/skills/markus-team-cli/skill.json +14 -0
  163. package/templates/skills/self-evolution/SKILL.md +207 -0
  164. package/templates/skills/self-evolution/skill.json +14 -0
  165. package/templates/skills/skill-building/SKILL.md +172 -0
  166. package/templates/skills/skill-building/skill.json +13 -0
  167. package/templates/skills/team-building/SKILL.md +159 -0
  168. package/templates/skills/team-building/skill.json +13 -0
  169. package/templates/teams/content-team/ANNOUNCEMENT.md +10 -0
  170. package/templates/teams/content-team/NORMS.md +20 -0
  171. package/templates/teams/content-team/team.json +18 -0
  172. package/templates/teams/dev-squad/ANNOUNCEMENT.md +11 -0
  173. package/templates/teams/dev-squad/NORMS.md +22 -0
  174. package/templates/teams/dev-squad/team.json +19 -0
  175. package/templates/teams/startup-team/ANNOUNCEMENT.md +11 -0
  176. package/templates/teams/startup-team/NORMS.md +21 -0
  177. package/templates/teams/startup-team/team.json +20 -0
@@ -0,0 +1,628 @@
1
+ import { resolve, join, dirname } from 'node:path';
2
+ import { existsSync } from 'node:fs';
3
+ import { homedir } from 'node:os';
4
+ import { allTemplateDirs, resolveTemplatesDir, resolveWebUiDir } from '../paths.js';
5
+ import { loadConfig, getDefaultConfigPath, createLogger, } from '@markus/shared';
6
+ import { AgentManager, LLMRouter, LLMLogger, RoleLoader, createDefaultSkillRegistry, ExternalAgentGateway, } from '@markus/core';
7
+ import { OrganizationService, TaskService, APIServer, HITLService, BillingService, AuditService, ProjectService, RequirementService, KnowledgeService, FileKnowledgeStore, DeliverableService, ReportService, TrustService, ScheduledTaskRunner, initStorage, searchRegistries, installSkill, } from '@markus/org-manager';
8
+ import { MessageRouter, FeishuAdapter, WebUIAdapter } from '@markus/comms';
9
+ const log = createLogger('cli');
10
+ export function registerStartCommand(program) {
11
+ program
12
+ .command('start')
13
+ .description('Start the Markus server (auto-initializes on first run)')
14
+ .option('--setup', 'Force re-run the interactive setup wizard before starting')
15
+ .action(async (opts) => {
16
+ const globalOpts = program.optsWithGlobals();
17
+ const configPath = globalOpts.config ?? getDefaultConfigPath();
18
+ // Auto-detect first run: no config file → run setup wizard
19
+ if (opts.setup || !existsSync(configPath)) {
20
+ if (!existsSync(configPath)) {
21
+ console.log(' No configuration found — running first-time setup...\n');
22
+ }
23
+ const { quickInit } = await import('./init.js');
24
+ await quickInit();
25
+ }
26
+ const config = loadConfig(globalOpts.config);
27
+ await startServer(config, { port: globalOpts.port, config: globalOpts.config });
28
+ });
29
+ }
30
+ async function createServices(config) {
31
+ const templateDirs = allTemplateDirs('roles');
32
+ if (templateDirs.length === 0)
33
+ templateDirs.push(resolveTemplatesDir('roles'));
34
+ const roleLoader = new RoleLoader(templateDirs);
35
+ const providerConfigs = {};
36
+ let defaultProvider = config.llm.defaultProvider;
37
+ const llmTimeoutMs = Number(process.env['LLM_TIMEOUT_MS']) || config.llm.timeoutMs || undefined;
38
+ const anthropicKey = config.llm.providers['anthropic']?.apiKey ?? process.env['ANTHROPIC_API_KEY'];
39
+ if (anthropicKey) {
40
+ providerConfigs['anthropic'] = {
41
+ provider: 'anthropic',
42
+ model: config.llm.defaultModel,
43
+ apiKey: anthropicKey,
44
+ timeoutMs: llmTimeoutMs,
45
+ };
46
+ }
47
+ const openaiKey = config.llm.providers['openai']?.apiKey ?? process.env['OPENAI_API_KEY'];
48
+ if (openaiKey) {
49
+ providerConfigs['openai'] = {
50
+ provider: 'openai',
51
+ model: 'gpt-5.4',
52
+ apiKey: openaiKey,
53
+ timeoutMs: llmTimeoutMs,
54
+ };
55
+ }
56
+ const siliconflowKey = config.llm.providers['siliconflow']?.apiKey ?? process.env['SILICONFLOW_API_KEY'];
57
+ if (siliconflowKey) {
58
+ providerConfigs['siliconflow'] = {
59
+ provider: 'siliconflow',
60
+ model: process.env['SILICONFLOW_MODEL'] ?? 'Qwen/Qwen3.5-35B-A3B',
61
+ apiKey: siliconflowKey,
62
+ baseUrl: process.env['SILICONFLOW_BASE_URL'] ??
63
+ config.llm.providers['siliconflow']?.baseUrl ??
64
+ 'https://api.siliconflow.cn/v1',
65
+ timeoutMs: llmTimeoutMs,
66
+ };
67
+ if (config.llm.defaultProvider === 'siliconflow') {
68
+ defaultProvider = 'siliconflow';
69
+ }
70
+ }
71
+ const minimaxKey = config.llm.providers['minimax']?.apiKey ?? process.env['MINIMAX_API_KEY'];
72
+ if (minimaxKey) {
73
+ providerConfigs['minimax'] = {
74
+ provider: 'openai',
75
+ model: process.env['MINIMAX_MODEL'] ?? 'MiniMax-M2.7',
76
+ apiKey: minimaxKey,
77
+ baseUrl: process.env['MINIMAX_BASE_URL'] ??
78
+ config.llm.providers['minimax']?.baseUrl ??
79
+ 'https://api.minimax.io/v1',
80
+ timeoutMs: llmTimeoutMs,
81
+ };
82
+ if (config.llm.defaultProvider === 'minimax') {
83
+ defaultProvider = 'minimax';
84
+ }
85
+ }
86
+ const openrouterKey = config.llm.providers['openrouter']?.apiKey ?? process.env['OPENROUTER_API_KEY'];
87
+ if (openrouterKey) {
88
+ providerConfigs['openrouter'] = {
89
+ provider: 'openrouter',
90
+ model: process.env['OPENROUTER_MODEL'] ?? 'xiaomi/mimo-v2-pro',
91
+ apiKey: openrouterKey,
92
+ baseUrl: process.env['OPENROUTER_BASE_URL'] ??
93
+ config.llm.providers['openrouter']?.baseUrl ??
94
+ 'https://openrouter.ai/api/v1',
95
+ timeoutMs: llmTimeoutMs,
96
+ };
97
+ if (config.llm.defaultProvider === 'openrouter') {
98
+ defaultProvider = 'openrouter';
99
+ }
100
+ }
101
+ // If the configured default provider has no API key, fall back to the first available one
102
+ if (!providerConfigs[defaultProvider]) {
103
+ const available = Object.keys(providerConfigs);
104
+ if (available.length > 0) {
105
+ defaultProvider = available[0];
106
+ }
107
+ }
108
+ const llmRouter = LLMRouter.createDefault(providerConfigs, defaultProvider);
109
+ // Apply enabled/disabled state from config
110
+ for (const [name, provCfg] of Object.entries(config.llm.providers)) {
111
+ if (provCfg.enabled === false) {
112
+ llmRouter.setProviderEnabled(name, false);
113
+ }
114
+ }
115
+ // Wire LLM audit logging
116
+ const llmLogger = new LLMLogger();
117
+ llmRouter.setLogCallback((entry) => llmLogger.log(entry));
118
+ const skillDirs = allTemplateDirs('skills');
119
+ const skillRegistry = await createDefaultSkillRegistry({
120
+ extraSkillDirs: skillDirs,
121
+ });
122
+ const storage = await initStorage(config.database?.url);
123
+ const markusDataDir = join(homedir(), '.markus');
124
+ const sharedDataDir = join(markusDataDir, 'shared');
125
+ const taskService = new TaskService();
126
+ taskService.setSharedDataDir(sharedDataDir);
127
+ if (storage) {
128
+ taskService.setTaskRepo(storage.taskRepo);
129
+ taskService.setTaskLogRepo(storage.taskLogRepo);
130
+ if (storage.taskCommentRepo) {
131
+ taskService.setTaskCommentRepo(storage.taskCommentRepo);
132
+ }
133
+ await taskService.loadFromDB('default');
134
+ taskService.startTimeoutChecker();
135
+ }
136
+ const agentManager = new AgentManager({
137
+ llmRouter,
138
+ roleLoader,
139
+ dataDir: join(markusDataDir, 'agents'),
140
+ sharedDataDir,
141
+ skillRegistry,
142
+ taskService,
143
+ mcpServers: config.mcpServers,
144
+ });
145
+ taskService.setAgentManager(agentManager);
146
+ const orgService = new OrganizationService(agentManager, roleLoader, storage ?? undefined);
147
+ taskService.setOrgService(orgService);
148
+ await orgService.createOrganization(config.org.name, 'default', 'default');
149
+ orgService.addHumanUser('default', 'Owner', 'owner', { id: 'default' });
150
+ const hitlService = new HITLService();
151
+ taskService.setHITLService(hitlService);
152
+ const billingService = new BillingService();
153
+ billingService.setOrgPlan('default', 'free');
154
+ const auditService = new AuditService();
155
+ taskService.setAuditService(auditService);
156
+ return {
157
+ agentManager,
158
+ orgService,
159
+ taskService,
160
+ roleLoader,
161
+ llmRouter,
162
+ skillRegistry,
163
+ hitlService,
164
+ billingService,
165
+ auditService,
166
+ };
167
+ }
168
+ async function startServer(config, values) {
169
+ console.log('Starting Markus server...');
170
+ // Ensure `markus` is available in PATH for agents invoking it via shell_execute.
171
+ // In npm-global mode: process.argv[1] is the installed binary (e.g. /usr/local/bin/markus)
172
+ // In source-dev mode: node_modules/.bin contains the symlink
173
+ const currentPath = process.env['PATH'] ?? '';
174
+ const extraPaths = [];
175
+ const selfBinDir = dirname(resolve(process.argv[1] ?? ''));
176
+ if (selfBinDir && !currentPath.includes(selfBinDir))
177
+ extraPaths.push(selfBinDir);
178
+ const cwdBin = join(process.cwd(), 'node_modules', '.bin');
179
+ if (existsSync(cwdBin) && !currentPath.includes(cwdBin))
180
+ extraPaths.push(cwdBin);
181
+ if (extraPaths.length > 0) {
182
+ process.env['PATH'] = `${extraPaths.join(':')}:${currentPath}`;
183
+ }
184
+ // Propagate markus.json security settings into env so downstream services can read them
185
+ if (config.security?.adminPassword && !process.env['ADMIN_PASSWORD']) {
186
+ process.env['ADMIN_PASSWORD'] = config.security.adminPassword;
187
+ }
188
+ // Propagate markus.json search API keys into env so web_search tool can read them
189
+ if (config.integrations?.search?.serperApiKey && !process.env['SERPER_API_KEY']) {
190
+ process.env['SERPER_API_KEY'] = config.integrations.search.serperApiKey;
191
+ }
192
+ if (config.integrations?.search?.braveApiKey && !process.env['BRAVE_SEARCH_API_KEY']) {
193
+ process.env['BRAVE_SEARCH_API_KEY'] = config.integrations.search.braveApiKey;
194
+ }
195
+ const { orgService, taskService, agentManager, llmRouter, skillRegistry, hitlService, billingService, auditService, } = await createServices(config);
196
+ const apiPort = Number(values['port']) || config.server.apiPort;
197
+ const apiServer = new APIServer(orgService, taskService, apiPort);
198
+ apiServer.setSkillRegistry(skillRegistry);
199
+ apiServer.setHITLService(hitlService);
200
+ apiServer.setBillingService(billingService);
201
+ apiServer.setAuditService(auditService);
202
+ const projectService = new ProjectService();
203
+ const storage = orgService.getStorage();
204
+ if (storage?.projectRepo) {
205
+ projectService.setProjectRepo(storage.projectRepo);
206
+ }
207
+ await projectService.loadFromDB('default');
208
+ const knowledgeStore = new FileKnowledgeStore(join(homedir(), '.markus', 'knowledge'));
209
+ const knowledgeService = new KnowledgeService(knowledgeStore);
210
+ const deliverableService = new DeliverableService(storage?.deliverableRepo);
211
+ await deliverableService.load();
212
+ // One-time migration: sync existing task.deliverables into the unified deliverables table
213
+ const allTasks = taskService.listTasks({ orgId: 'default' });
214
+ await deliverableService.migrateFromTasks(allTasks);
215
+ const reportService = new ReportService(taskService, billingService, auditService, knowledgeService);
216
+ const _trustService = new TrustService();
217
+ const requirementService = new RequirementService();
218
+ if (storage?.requirementRepo) {
219
+ requirementService.setRequirementRepo(storage.requirementRepo);
220
+ }
221
+ await requirementService.loadFromStorage('default');
222
+ agentManager.setRequirementService(requirementService);
223
+ agentManager.setProjectService(projectService);
224
+ agentManager.setKnowledgeService(knowledgeService);
225
+ agentManager.setDeliverableService(deliverableService);
226
+ apiServer.setProjectService(projectService);
227
+ apiServer.setReportService(reportService);
228
+ apiServer.setKnowledgeService(knowledgeService);
229
+ apiServer.setDeliverableService(deliverableService);
230
+ apiServer.setRequirementService(requirementService);
231
+ taskService.setDeliverableService(deliverableService);
232
+ // Wire ProjectService into TaskService (worktree management is handled by agents)
233
+ taskService.setProjectService(projectService);
234
+ taskService.setRequirementService(requirementService);
235
+ // Expose LLM router to API server so settings can read/write it at runtime
236
+ apiServer.setLLMRouter(llmRouter);
237
+ apiServer.setConfigPath(values['config'] ?? getDefaultConfigPath());
238
+ if (config.hub?.url)
239
+ apiServer.setHubUrl(config.hub.url);
240
+ // Serve pre-built Web UI if available
241
+ const webUiDir = resolveWebUiDir();
242
+ if (webUiDir) {
243
+ apiServer.setWebUiDir(webUiDir);
244
+ log.info('Web UI static files enabled', { dir: webUiDir });
245
+ }
246
+ // Wire storage for chat persistence and auth
247
+ const firstOrgId = 'default';
248
+ if (storage) {
249
+ apiServer.setStorage(storage);
250
+ await apiServer.ensureAdminUser(firstOrgId);
251
+ // Restore persisted teams, agents, and users from DB
252
+ await orgService.loadFromDB(firstOrgId);
253
+ }
254
+ // Seed default team + Secretary agent (runs for both DB and in-memory mode)
255
+ await orgService.seedDefaultTeam(firstOrgId, 'default');
256
+ // Ensure builder agents exist (idempotent — skips if already created)
257
+ await orgService.seedBuilderAgents(firstOrgId, skillRegistry);
258
+ // Wire skill search/install callbacks so agents can discover and install remote skills
259
+ agentManager.setSkillSearcher(async (query) => searchRegistries(query));
260
+ agentManager.setSkillInstaller(async (request) => {
261
+ const result = await installSkill({
262
+ name: request.name ?? '',
263
+ source: request.source,
264
+ slug: request.slug,
265
+ sourceUrl: request.sourceUrl,
266
+ description: request.description,
267
+ category: request.category,
268
+ version: request.version,
269
+ githubRepo: request.githubRepo,
270
+ githubSkillPath: request.githubSkillPath,
271
+ }, skillRegistry);
272
+ return { installed: result.installed, name: result.name, method: result.method };
273
+ });
274
+ // Wire proactive user message senders for all agents
275
+ if (storage?.chatSessionRepo) {
276
+ const ws = apiServer.getWSBroadcaster();
277
+ for (const info of agentManager.listAgents()) {
278
+ agentManager.setUserMessageSender(info.id, async (message) => {
279
+ const sessions = await storage.chatSessionRepo.getSessionsByAgent(info.id);
280
+ let sessionId;
281
+ if (sessions.length > 0) {
282
+ sessionId = sessions[0].id;
283
+ }
284
+ else {
285
+ const newSess = await storage.chatSessionRepo.createSession(info.id);
286
+ sessionId = newSess.id;
287
+ }
288
+ const msg = await storage.chatSessionRepo.appendMessage(sessionId, info.id, 'assistant', message);
289
+ ws.broadcastProactiveMessage(info.id, info.name, sessionId, msg.id, message);
290
+ return { sessionId, messageId: msg.id };
291
+ });
292
+ }
293
+ }
294
+ // Auto-resume in_progress tasks after agents are fully loaded.
295
+ // Tasks retain their execution history in DB (task_logs + comments),
296
+ // so the agent receives full previous context on resume.
297
+ setTimeout(async () => {
298
+ try {
299
+ await taskService.resumeInProgressTasks();
300
+ }
301
+ catch (err) {
302
+ log.warn('Failed to auto-resume in_progress tasks', { error: String(err) });
303
+ }
304
+ }, 3000);
305
+ // Wire External Agent Gateway for OpenClaw integration
306
+ const gatewaySecret = config.security?.gatewaySecret ?? process.env['GATEWAY_SECRET'] ?? 'markus-gateway-default-secret-change-me';
307
+ const gateway = new ExternalAgentGateway({ signingSecret: gatewaySecret });
308
+ gateway.setAgentCreator(async (opts) => {
309
+ const caps = (opts.capabilities ?? []).map((c) => c.toLowerCase());
310
+ const roleFromCaps = (cs) => {
311
+ if (cs.some(c => c.includes('review')))
312
+ return 'reviewer';
313
+ if (cs.some(c => c.includes('test') || c.includes('qa')))
314
+ return 'qa-engineer';
315
+ if (cs.some(c => c.includes('devops') || c.includes('deploy') || c.includes('infra')))
316
+ return 'devops';
317
+ if (cs.some(c => c.includes('product')))
318
+ return 'product-manager';
319
+ if (cs.some(c => c.includes('project') || c.includes('manage')))
320
+ return 'project-manager';
321
+ if (cs.some(c => c.includes('support')))
322
+ return 'support';
323
+ if (cs.some(c => c.includes('content') || c.includes('write') || c.includes('doc')))
324
+ return 'content-writer';
325
+ if (cs.some(c => c.includes('market')))
326
+ return 'marketing';
327
+ if (cs.some(c => c.includes('research')))
328
+ return 'research-assistant';
329
+ return 'developer';
330
+ };
331
+ const agent = await agentManager.createAgent({
332
+ name: opts.name,
333
+ roleName: roleFromCaps(caps),
334
+ orgId: opts.orgId,
335
+ });
336
+ // Persist to DB so chat_sessions FK constraint is satisfied
337
+ if (storage?.agentRepo) {
338
+ try {
339
+ await storage.agentRepo.create({
340
+ id: agent.id,
341
+ name: agent.config.name,
342
+ orgId: opts.orgId,
343
+ roleId: agent.config.roleId,
344
+ roleName: agent.role.name,
345
+ agentRole: 'worker',
346
+ skills: agent.config.skills,
347
+ llmConfig: agent.config.llmConfig,
348
+ heartbeatIntervalMs: agent.config.heartbeatIntervalMs,
349
+ });
350
+ }
351
+ catch (err) {
352
+ log.warn('Failed to persist gateway agent to DB (may already exist)', { error: String(err) });
353
+ }
354
+ }
355
+ return { id: agent.id };
356
+ });
357
+ // Persistence: wire the store so registrations survive restarts
358
+ if (storage?.externalAgentRepo) {
359
+ const repo = storage.externalAgentRepo;
360
+ const gatewayStore = {
361
+ async saveRegistration(reg) { await repo.save(reg); },
362
+ async deleteRegistration(extId, orgId) { return repo.delete(extId, orgId); },
363
+ async updateRegistration(extId, orgId, patch) { await repo.update(extId, orgId, patch); },
364
+ async loadAll() { return repo.loadAll(); },
365
+ };
366
+ gateway.setStore(gatewayStore);
367
+ await gateway.loadFromStore((id) => { try {
368
+ agentManager.getAgent(id);
369
+ return true;
370
+ }
371
+ catch {
372
+ return false;
373
+ } });
374
+ }
375
+ gateway.setMessageRouter(async (markusAgentId, message, _senderId) => {
376
+ const agent = agentManager.getAgent(markusAgentId);
377
+ const reply = await agent.handleMessage(message);
378
+ return reply ?? 'No response';
379
+ });
380
+ gateway.setTasksFetcher((agentId) => {
381
+ return taskService.getTasksByAgent(agentId).map(t => ({
382
+ id: t.id,
383
+ title: t.title,
384
+ status: t.status,
385
+ priority: t.priority,
386
+ }));
387
+ });
388
+ apiServer.setGateway(gateway, gatewaySecret);
389
+ log.info('External Agent Gateway enabled', { secret: gatewaySecret === 'markus-gateway-default-secret-change-me' ? '(default)' : '(custom)' });
390
+ apiServer.start();
391
+ taskService.setWSBroadcaster(apiServer.getWSBroadcaster());
392
+ requirementService.setWSBroadcaster(apiServer.getWSBroadcaster());
393
+ deliverableService.setWSBroadcaster(apiServer.getWSBroadcaster());
394
+ const scheduledTaskRunner = new ScheduledTaskRunner(taskService);
395
+ scheduledTaskRunner.start();
396
+ agentManager.setEscalationHandler((agentId, reason) => {
397
+ log.warn('Agent escalation', { agentId, reason });
398
+ hitlService.notify({
399
+ targetUserId: 'default',
400
+ type: 'system',
401
+ title: 'Agent needs help',
402
+ body: reason,
403
+ priority: 'high',
404
+ });
405
+ auditService.record({
406
+ orgId: 'default',
407
+ agentId,
408
+ type: 'error',
409
+ action: 'escalation',
410
+ detail: reason,
411
+ success: false,
412
+ });
413
+ });
414
+ agentManager.setApprovalHandler(async (agentId, request) => {
415
+ const agents = agentManager.listAgents();
416
+ const agentName = agents.find(a => a.id === agentId)?.name ?? agentId;
417
+ const approved = await hitlService.requestApprovalAndWait({
418
+ agentId,
419
+ agentName,
420
+ type: 'action',
421
+ title: `Tool: ${request.toolName}`,
422
+ description: request.reason,
423
+ details: { toolName: request.toolName, toolArgs: request.toolArgs },
424
+ targetUserId: 'default',
425
+ expiresInMs: 5 * 60 * 1000, // 5 minutes
426
+ });
427
+ auditService.record({
428
+ orgId: 'default',
429
+ agentId,
430
+ type: 'approval_response',
431
+ action: request.toolName,
432
+ detail: approved ? 'approved' : 'rejected',
433
+ success: approved,
434
+ });
435
+ return approved;
436
+ });
437
+ agentManager.setAuditCallback((agentId, event) => {
438
+ auditService.record({
439
+ orgId: 'default',
440
+ agentId,
441
+ type: event.type,
442
+ action: event.action,
443
+ tokensUsed: event.tokensUsed,
444
+ durationMs: event.durationMs,
445
+ success: event.success,
446
+ detail: event.detail,
447
+ });
448
+ if (event.tokensUsed && event.type === 'llm_request') {
449
+ auditService.recordLLMUsage('default', agentId, Math.floor(event.tokensUsed * 0.7), Math.ceil(event.tokensUsed * 0.3));
450
+ billingService.recordUsage({
451
+ orgId: 'default',
452
+ agentId,
453
+ type: 'llm_tokens',
454
+ amount: event.tokensUsed,
455
+ metadata: { action: event.action },
456
+ });
457
+ }
458
+ if (event.type === 'tool_call') {
459
+ billingService.recordUsage({
460
+ orgId: 'default',
461
+ agentId,
462
+ type: 'tool_call',
463
+ amount: 1,
464
+ metadata: { action: event.action },
465
+ });
466
+ }
467
+ });
468
+ // Wire agent state changes to DB persistence + WS broadcast
469
+ if (storage) {
470
+ agentManager.setStateChangeHandler(async (agentId, state) => {
471
+ try {
472
+ await storage.agentRepo.updateStatus(agentId, state.status);
473
+ }
474
+ catch (err) {
475
+ log.warn('Failed to persist agent state', { agentId, error: String(err) });
476
+ }
477
+ // Broadcast status + currentActivity to all WS clients
478
+ apiServer.getWSBroadcaster().broadcastAgentUpdate(agentId, state.status, {
479
+ lastError: state.lastError,
480
+ lastErrorAt: state.lastErrorAt,
481
+ currentActivity: state.currentActivity,
482
+ });
483
+ });
484
+ }
485
+ // Wire activity persistence to SQLite
486
+ if (storage?.activityRepo) {
487
+ const actRepo = storage.activityRepo;
488
+ agentManager.setActivityCallbacks({
489
+ onStart: (activity) => {
490
+ try {
491
+ actRepo.insertActivity({
492
+ id: activity.id,
493
+ agentId: activity.agentId,
494
+ type: activity.type,
495
+ label: activity.label,
496
+ taskId: activity.taskId,
497
+ startedAt: activity.startedAt,
498
+ });
499
+ }
500
+ catch (err) {
501
+ log.warn('Failed to persist activity start', { activityId: activity.id, error: String(err) });
502
+ }
503
+ },
504
+ onLog: (data) => {
505
+ try {
506
+ actRepo.insertActivityLog(data);
507
+ }
508
+ catch (err) {
509
+ log.warn('Failed to persist activity log', { activityId: data.activityId, error: String(err) });
510
+ }
511
+ },
512
+ onEnd: (activityId, summary) => {
513
+ try {
514
+ actRepo.updateActivity(activityId, summary);
515
+ }
516
+ catch (err) {
517
+ log.warn('Failed to persist activity end', { activityId, error: String(err) });
518
+ }
519
+ },
520
+ });
521
+ }
522
+ // Wire agent activity logs to WS broadcast
523
+ agentManager.getEventBus().on('agent:activity_log', (event) => {
524
+ apiServer.getWSBroadcaster().broadcast({
525
+ type: 'agent:activity_log',
526
+ payload: event,
527
+ timestamp: new Date().toISOString(),
528
+ });
529
+ });
530
+ // Daily token reset scheduler: runs at midnight to reset per-agent tokensUsedToday
531
+ const scheduleDailyReset = () => {
532
+ const now = new Date();
533
+ const nextMidnight = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1, 0, 0, 0, 0);
534
+ const msUntilMidnight = nextMidnight.getTime() - now.getTime();
535
+ setTimeout(() => {
536
+ log.info('Daily token reset triggered');
537
+ for (const agentInfo of agentManager.listAgents()) {
538
+ try {
539
+ const agent = agentManager.getAgent(agentInfo.id);
540
+ agent.resetDailyTokens();
541
+ }
542
+ catch {
543
+ /* agent may be offline */
544
+ }
545
+ }
546
+ scheduleDailyReset();
547
+ }, msUntilMidnight);
548
+ log.info(`Daily token reset scheduled in ${Math.round(msUntilMidnight / 60000)} minutes`);
549
+ };
550
+ scheduleDailyReset();
551
+ const messageRouter = new MessageRouter();
552
+ const webUIAdapter = new WebUIAdapter();
553
+ messageRouter.registerAdapter(webUIAdapter);
554
+ messageRouter.setAgentHandler(async (agentId, message) => {
555
+ const startTs = Date.now();
556
+ try {
557
+ const agent = agentManager.getAgent(agentId);
558
+ const reply = await agent.handleMessage(message.content.text ?? '', message.senderId);
559
+ auditService.record({
560
+ orgId: 'default',
561
+ agentId,
562
+ type: 'agent_message',
563
+ action: 'handle_message',
564
+ detail: (message.content.text ?? '').slice(0, 200),
565
+ durationMs: Date.now() - startTs,
566
+ success: true,
567
+ });
568
+ return reply;
569
+ }
570
+ catch (error) {
571
+ auditService.record({
572
+ orgId: 'default',
573
+ agentId,
574
+ type: 'agent_message',
575
+ action: 'handle_message',
576
+ detail: String(error).slice(0, 200),
577
+ durationMs: Date.now() - startTs,
578
+ success: false,
579
+ });
580
+ log.error('Agent message handler error', { error: String(error) });
581
+ return undefined;
582
+ }
583
+ });
584
+ const commPort = apiPort + 2;
585
+ await messageRouter.connectAll([{ platform: 'webui', port: commPort }]);
586
+ // Check for Feishu config
587
+ const feishuAppId = config.integrations?.feishu?.appId ?? process.env['FEISHU_APP_ID'];
588
+ const feishuAppSecret = config.integrations?.feishu?.appSecret ?? process.env['FEISHU_APP_SECRET'];
589
+ if (feishuAppId && feishuAppSecret) {
590
+ const feishuAdapter = new FeishuAdapter();
591
+ messageRouter.registerAdapter(feishuAdapter);
592
+ await messageRouter.connectAll([
593
+ {
594
+ platform: 'feishu',
595
+ appId: feishuAppId,
596
+ appSecret: feishuAppSecret,
597
+ },
598
+ ]);
599
+ console.log(' Feishu integration enabled');
600
+ }
601
+ const webPort = config.server.webPort;
602
+ const webUiLine = webUiDir
603
+ ? ` Web UI: http://localhost:${apiPort} (built-in)`
604
+ : ` Web UI: http://localhost:${webPort} (run: pnpm --filter @markus/web-ui dev)`;
605
+ console.log(`
606
+ Markus is running!
607
+
608
+ API Server: http://localhost:${apiPort}
609
+ ${webUiLine}
610
+ WebUI Comm: http://localhost:${commPort}
611
+
612
+ Press Ctrl+C to stop.
613
+ `);
614
+ // Start restored agents in background (server is already accepting requests)
615
+ orgService.startRestoredAgentsInBackground();
616
+ process.on('SIGINT', () => {
617
+ console.log('\nShutting down...');
618
+ scheduledTaskRunner.stop();
619
+ apiServer.stop();
620
+ agentManager.shutdown()
621
+ .then(() => messageRouter.disconnectAll())
622
+ .then(() => process.exit(0))
623
+ .catch(() => process.exit(1));
624
+ });
625
+ // Keep alive
626
+ await new Promise(() => { });
627
+ }
628
+ //# sourceMappingURL=start.js.map