@markus-global/cli 0.4.18 → 0.4.20

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.
@@ -1 +1 @@
1
- {"version":3,"file":"start.d.ts","sourceRoot":"","sources":["../../src/commands/start.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAkDzC,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,QAqBpD"}
1
+ {"version":3,"file":"start.d.ts","sourceRoot":"","sources":["../../src/commands/start.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAwDzC,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,QAqBpD"}
@@ -2,9 +2,9 @@ import { resolve, join, dirname } from 'node:path';
2
2
  import { existsSync } from 'node:fs';
3
3
  import { homedir } from 'node:os';
4
4
  import { allTemplateDirs, resolveTemplatesDir, resolveWebUiDir } from '../paths.js';
5
- import { loadConfig, getDefaultConfigPath, createLogger, closeRuntimeLogger, } from '@markus/shared';
5
+ import { loadConfig, getDefaultConfigPath, createLogger, closeRuntimeLogger, checkForUpdate, TRIAGE_MAX_TOKENS, TRIAGE_TEMPERATURE, TRIAGE_ALLOWED_TOOLS, } from '@markus/shared';
6
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';
7
+ import { OrganizationService, TaskService, APIServer, HITLService, BillingService, AuditService, ProjectService, RequirementService, KnowledgeService, FileKnowledgeStore, DeliverableService, ReportService, TrustService, ArchiveService, ScheduledTaskRunner, initStorage, searchRegistries, installSkill, } from '@markus/org-manager';
8
8
  import { MessageRouter, FeishuAdapter, WebUIAdapter } from '@markus/comms';
9
9
  import { initStartupLogger, startupLog, startupBlank, startupSection, closeStartupLogger, getStartupLogFile } from '../utils/logger.js';
10
10
  import { openBrowser } from '../utils/browser.js';
@@ -177,6 +177,15 @@ async function createServices(config) {
177
177
  if (config.agent?.maxToolIterations) {
178
178
  agentManager.maxToolIterations = config.agent.maxToolIterations;
179
179
  }
180
+ if (config.browser?.bringToFront !== undefined) {
181
+ agentManager.setBrowserBringToFront(config.browser.bringToFront);
182
+ }
183
+ if (config.browser?.autoCloseTabs !== undefined) {
184
+ agentManager.setBrowserAutoCloseTabs(config.browser.autoCloseTabs);
185
+ }
186
+ if (config.browser?.remoteDebuggingPort) {
187
+ agentManager.setBrowserRemoteDebuggingPort(config.browser.remoteDebuggingPort);
188
+ }
180
189
  taskService.setAgentManager(agentManager);
181
190
  const orgService = new OrganizationService(agentManager, roleLoader, storage ?? undefined);
182
191
  taskService.setOrgService(orgService);
@@ -306,6 +315,9 @@ async function startServer(config, values) {
306
315
  if (storage?.notificationRepo) {
307
316
  hitlService.setNotificationRepo(storage.notificationRepo);
308
317
  }
318
+ if (storage?.approvalRepo) {
319
+ hitlService.setApprovalRepo(storage.approvalRepo);
320
+ }
309
321
  if (storage?.projectRepo) {
310
322
  projectService.setProjectRepo(storage.projectRepo);
311
323
  }
@@ -340,6 +352,10 @@ async function startServer(config, values) {
340
352
  // Wire ProjectService into TaskService (workspace management is handled by agents)
341
353
  taskService.setProjectService(projectService);
342
354
  taskService.setRequirementService(requirementService);
355
+ // Auto-archive: archive terminal tasks and requirements after configured thresholds
356
+ const archiveService = new ArchiveService(taskService, projectService);
357
+ archiveService.setRequirementService(requirementService);
358
+ archiveService.start();
343
359
  // Expose LLM router to API server so settings can read/write it at runtime
344
360
  apiServer.setLLMRouter(llmRouter);
345
361
  apiServer.setConfigPath(values['config'] ?? getDefaultConfigPath());
@@ -372,6 +388,16 @@ async function startServer(config, values) {
372
388
  if (hubClient) {
373
389
  agentManager.setHubClient(hubClient);
374
390
  }
391
+ // Wire team/agent update callbacks for manager tools
392
+ agentManager.setTeamUpdater(async (teamId, data) => {
393
+ const team = await orgService.updateTeam(teamId, data);
394
+ return { id: teamId, name: team.name, description: team.description };
395
+ });
396
+ if (storage) {
397
+ agentManager.setAgentConfigPersister(async (agentId, data) => {
398
+ await storage.agentRepo.updateConfig(agentId, data);
399
+ });
400
+ }
375
401
  // Wire skill search/install callbacks so agents can discover and install remote skills
376
402
  agentManager.setSkillSearcher(async (query) => searchRegistries(query));
377
403
  agentManager.setSkillInstaller(async (request) => {
@@ -388,50 +414,20 @@ async function startServer(config, values) {
388
414
  }, skillRegistry);
389
415
  return { installed: result.installed, name: result.name, method: result.method };
390
416
  });
391
- // Wire proactive user message senders and chat session fetchers for agents
392
- if (storage?.chatSessionRepo) {
393
- const ws = apiServer.getWSBroadcaster();
394
- const wireAgentChatIntegration = (agentId) => {
395
- try {
396
- const agent = agentManager.getAgent(agentId);
397
- agentManager.setUserMessageSender(agentId, async (message, opts) => {
398
- let sessionId;
399
- if (opts?.sessionId) {
400
- sessionId = opts.sessionId;
401
- }
402
- else {
403
- const sessions = await storage.chatSessionRepo.getSessionsByAgent(agentId);
404
- if (sessions.length > 0) {
405
- sessionId = sessions[0].id;
406
- }
407
- else {
408
- const newSess = await storage.chatSessionRepo.createSession(agentId);
409
- sessionId = newSess.id;
410
- }
411
- }
412
- const msg = await storage.chatSessionRepo.appendMessage(sessionId, agentId, 'assistant', message);
413
- ws.broadcastProactiveMessage(agentId, agent.config.name, sessionId, msg.id, message);
414
- return { sessionId, messageId: msg.id };
415
- });
416
- agentManager.setChatSessionsFetcher(agentId, async () => {
417
- const sessions = await storage.chatSessionRepo.getSessionsByAgent(agentId);
418
- return sessions.slice(0, 5).map((s) => ({
419
- id: s.id,
420
- title: s.title,
421
- lastMessageAt: s.lastMessageAt ?? s.createdAt ?? new Date().toISOString(),
422
- lastMessagePreview: s.lastMessagePreview,
423
- }));
424
- });
425
- }
426
- catch { /* agent not found */ }
427
- };
428
- for (const info of agentManager.listAgents())
429
- wireAgentChatIntegration(info.id);
430
- agentManager.getEventBus().on('agent:created', (evt) => {
431
- const { agentId } = evt;
432
- wireAgentChatIntegration(agentId);
417
+ // Wire user approval requester through HITL service
418
+ agentManager.setUserApprovalRequester(async (opts) => {
419
+ return hitlService.requestApprovalAndWait({
420
+ agentId: opts.agentId,
421
+ agentName: opts.agentName,
422
+ type: 'custom',
423
+ title: opts.title,
424
+ description: opts.description,
425
+ targetUserId: 'default',
426
+ options: opts.options,
427
+ allowFreeform: opts.allowFreeform,
428
+ details: { priority: opts.priority, taskId: opts.relatedTaskId },
433
429
  });
434
- }
430
+ });
435
431
  // Wire user notifier through HITL service
436
432
  agentManager.setUserNotifier((opts) => {
437
433
  hitlService.notify({
@@ -447,6 +443,13 @@ async function startServer(config, values) {
447
443
  });
448
444
  // Ensure every agent has a main session on startup, then persist activity logs to it
449
445
  if (storage?.chatSessionRepo) {
446
+ // Migrate legacy assistant messages that lack segments metadata
447
+ try {
448
+ storage.chatSessionRepo.migrateLegacyMessages();
449
+ }
450
+ catch (e) {
451
+ log.warn('Legacy chat message migration failed', { error: String(e) });
452
+ }
450
453
  for (const info of agentManager.listAgents()) {
451
454
  try {
452
455
  storage.chatSessionRepo.getOrCreateMainSession(info.id);
@@ -457,15 +460,77 @@ async function startServer(config, values) {
457
460
  agentManager.getEventBus().on('agent:activity-log', async (evt) => {
458
461
  const { agentId, message, metadata } = evt;
459
462
  try {
460
- const mainSession = await storage.chatSessionRepo.getOrCreateMainSession(agentId);
461
- const msg = await storage.chatSessionRepo.appendMessage(mainSession.id, agentId, 'assistant', message, 0, metadata);
463
+ const mainSession = storage.chatSessionRepo.getOrCreateMainSession(agentId);
464
+ const msg = storage.chatSessionRepo.appendMessage(mainSession.id, agentId, 'assistant', message, 0, metadata);
465
+ storage.chatSessionRepo.updateLastMessage(mainSession.id);
462
466
  const agent = agentManager.getAgent(agentId);
463
- ws.broadcastProactiveMessage(agentId, agent.config.name, mainSession.id, msg.id, message);
467
+ ws.broadcastProactiveMessage(agentId, agent.config.name, mainSession.id, msg.id, message, {
468
+ ...metadata,
469
+ isMainSession: true,
470
+ });
464
471
  }
465
472
  catch (e) {
466
473
  log.warn('Failed to persist activity log', { agentId, error: String(e) });
467
474
  }
468
475
  });
476
+ // notify_user: persist as regular chat message + WS broadcast + notification bell
477
+ agentManager.getEventBus().on('agent:notify-user', async (evt) => {
478
+ const { agentId, title, body, priority, taskId, requirementId } = evt;
479
+ try {
480
+ const mainSession = storage.chatSessionRepo.getOrCreateMainSession(agentId);
481
+ const agent = agentManager.getAgent(agentId);
482
+ const formattedMsg = `**${title}**\n\n${body}`;
483
+ const msg = storage.chatSessionRepo.appendMessage(mainSession.id, agentId, 'assistant', formattedMsg, 0, {});
484
+ storage.chatSessionRepo.updateLastMessage(mainSession.id);
485
+ ws.broadcastProactiveMessage(agentId, agent.config.name, mainSession.id, msg.id, formattedMsg, {
486
+ isMainSession: true,
487
+ });
488
+ const hasTask = !!taskId;
489
+ hitlService.notify({
490
+ targetUserId: 'default',
491
+ type: 'agent_report',
492
+ title, body, priority,
493
+ actionType: hasTask ? 'navigate' : 'open_chat',
494
+ actionTarget: hasTask
495
+ ? JSON.stringify({ path: `/work?openTask=${taskId}` })
496
+ : JSON.stringify({ agentId, sessionId: mainSession.id }),
497
+ metadata: { agentId, agentName: agent.config.name, taskId, requirementId, sessionId: mainSession.id },
498
+ });
499
+ }
500
+ catch (e) {
501
+ log.warn('Failed to handle notify-user event', { agentId, error: String(e) });
502
+ }
503
+ });
504
+ // escalation: persist as regular chat message + WS broadcast + notification + audit
505
+ agentManager.getEventBus().on('agent:escalation', async (evt) => {
506
+ const { agentId, reason } = evt;
507
+ try {
508
+ const mainSession = storage.chatSessionRepo.getOrCreateMainSession(agentId);
509
+ const agent = agentManager.getAgent(agentId);
510
+ const formattedMsg = `**I need help**\n\n${reason}`;
511
+ const msg = storage.chatSessionRepo.appendMessage(mainSession.id, agentId, 'assistant', formattedMsg, 0, {});
512
+ storage.chatSessionRepo.updateLastMessage(mainSession.id);
513
+ ws.broadcastProactiveMessage(agentId, agent.config.name, mainSession.id, msg.id, formattedMsg, {
514
+ isMainSession: true,
515
+ });
516
+ hitlService.notify({
517
+ targetUserId: 'default',
518
+ type: 'system',
519
+ title: 'Agent needs help',
520
+ body: reason,
521
+ priority: 'high',
522
+ actionType: 'open_chat',
523
+ actionTarget: JSON.stringify({ agentId, sessionId: mainSession.id }),
524
+ metadata: { agentId, sessionId: mainSession.id },
525
+ });
526
+ auditService.record({
527
+ orgId: 'default', agentId, type: 'error', action: 'escalation', detail: reason, success: false,
528
+ });
529
+ }
530
+ catch (e) {
531
+ log.warn('Failed to handle escalation event', { agentId, error: String(e) });
532
+ }
533
+ });
469
534
  // Also create main session for newly created agents
470
535
  agentManager.getEventBus().on('agent:created', (evt) => {
471
536
  const { agentId } = evt;
@@ -475,17 +540,7 @@ async function startServer(config, values) {
475
540
  catch { /* skip */ }
476
541
  });
477
542
  }
478
- // Auto-resume in_progress tasks after agents are fully loaded.
479
- // Tasks retain their execution history in DB (task_logs + comments),
480
- // so the agent receives full previous context on resume.
481
- setTimeout(async () => {
482
- try {
483
- await taskService.resumeInProgressTasks();
484
- }
485
- catch (err) {
486
- log.warn('Failed to auto-resume in_progress tasks', { error: String(err) });
487
- }
488
- }, 3000);
543
+ // Task resume is triggered after agents finish starting (see below).
489
544
  // Wire External Agent Gateway for OpenClaw integration
490
545
  const gatewaySecret = config.security?.gatewaySecret ?? process.env['GATEWAY_SECRET'] ?? 'markus-gateway-default-secret-change-me';
491
546
  const gateway = new ExternalAgentGateway({ signingSecret: gatewaySecret });
@@ -577,23 +632,10 @@ async function startServer(config, values) {
577
632
  deliverableService.setWSBroadcaster(apiServer.getWSBroadcaster());
578
633
  const scheduledTaskRunner = new ScheduledTaskRunner(taskService);
579
634
  scheduledTaskRunner.start();
635
+ // Escalation callback kept for agent-internal state management; actual notification/DB/WS/audit
636
+ // logic is handled by the 'agent:escalation' event handler registered above.
580
637
  agentManager.setEscalationHandler((agentId, reason) => {
581
638
  log.warn('Agent escalation', { agentId, reason });
582
- hitlService.notify({
583
- targetUserId: 'default',
584
- type: 'system',
585
- title: 'Agent needs help',
586
- body: reason,
587
- priority: 'high',
588
- });
589
- auditService.record({
590
- orgId: 'default',
591
- agentId,
592
- type: 'error',
593
- action: 'escalation',
594
- detail: reason,
595
- success: false,
596
- });
597
639
  });
598
640
  agentManager.setApprovalHandler(async (agentId, request) => {
599
641
  const agents = agentManager.listAgents();
@@ -611,7 +653,6 @@ async function startServer(config, values) {
611
653
  description: request.reason,
612
654
  details: { ...request.toolArgs, toolName: request.toolName, agentId, taskId: request.taskId },
613
655
  targetUserId: 'default',
614
- expiresInMs: 5 * 60 * 1000, // 5 minutes
615
656
  });
616
657
  auditService.record({
617
658
  orgId: 'default',
@@ -725,6 +766,24 @@ async function startServer(config, values) {
725
766
  }
726
767
  },
727
768
  });
769
+ // Wire recall_activity tool to query execution history from SQLite
770
+ agentManager.setRecallCallbacks({
771
+ listActivities: (agentId, opts) => {
772
+ const results = actRepo.queryActivities(agentId, {
773
+ type: opts.type,
774
+ limit: opts.limit,
775
+ });
776
+ if (opts.taskId)
777
+ return results.filter((a) => a.taskId === opts.taskId);
778
+ return results;
779
+ },
780
+ getActivityLogs: (activityId) => {
781
+ return actRepo.getActivityLogs(activityId);
782
+ },
783
+ searchActivities: (agentId, query, opts) => {
784
+ return actRepo.searchActivities(agentId, query, opts);
785
+ },
786
+ });
728
787
  }
729
788
  // Wire mailbox + decision persistence to SQLite
730
789
  if (storage?.mailboxRepo && storage?.decisionRepo) {
@@ -737,11 +796,12 @@ async function startServer(config, values) {
737
796
  mailbox.setPersistence({
738
797
  save: (item) => {
739
798
  try {
799
+ const { responsePromise, ...persistableMetadata } = (item.metadata ?? {});
740
800
  mbRepo.save({
741
801
  id: item.id, agentId: item.agentId, sourceType: item.sourceType,
742
802
  priority: item.priority, status: item.status,
743
803
  payload: item.payload,
744
- metadata: (item.metadata ?? {}),
804
+ metadata: persistableMetadata,
745
805
  queuedAt: item.queuedAt,
746
806
  });
747
807
  }
@@ -758,10 +818,46 @@ async function startServer(config, values) {
758
818
  }
759
819
  },
760
820
  markStaleProcessingAsDropped: (aid) => mbRepo.markStaleProcessingAsDropped(aid),
821
+ loadQueued: (aid) => {
822
+ const rows = mbRepo.getByAgent(aid, { status: 'queued' });
823
+ return rows.map((r) => ({
824
+ id: r.id,
825
+ agentId: r.agentId,
826
+ sourceType: r.sourceType,
827
+ priority: r.priority,
828
+ status: r.status,
829
+ payload: r.payload,
830
+ metadata: r.metadata,
831
+ queuedAt: r.queuedAt,
832
+ startedAt: r.startedAt ?? undefined,
833
+ completedAt: r.completedAt ?? undefined,
834
+ deferredUntil: r.deferredUntil ?? undefined,
835
+ mergedInto: r.mergedInto ?? undefined,
836
+ retryCount: r.retryCount ?? 0,
837
+ }));
838
+ },
839
+ loadDeferred: (aid) => {
840
+ const rows = mbRepo.getByAgent(aid, { status: 'deferred' });
841
+ return rows.map((r) => ({
842
+ id: r.id,
843
+ agentId: r.agentId,
844
+ sourceType: r.sourceType,
845
+ priority: r.priority,
846
+ status: r.status,
847
+ payload: r.payload,
848
+ metadata: r.metadata,
849
+ queuedAt: r.queuedAt,
850
+ startedAt: r.startedAt ?? undefined,
851
+ completedAt: r.completedAt ?? undefined,
852
+ deferredUntil: r.deferredUntil ?? undefined,
853
+ mergedInto: r.mergedInto ?? undefined,
854
+ retryCount: r.retryCount ?? 0,
855
+ }));
856
+ },
761
857
  });
762
- const dropped = mailbox.recoverStaleItems();
763
- if (dropped > 0)
764
- log.info('Recovered stale processing mailbox items', { agentId, dropped });
858
+ const { dropped, restored, expired, merged } = mailbox.recoverStaleItems();
859
+ if (dropped > 0 || restored > 0 || expired > 0 || merged > 0)
860
+ log.info('Mailbox recovery on startup', { agentId, dropped, restored, expired, merged });
765
861
  agent.getAttentionController().setDecisionPersistence({
766
862
  save: (decision) => {
767
863
  try {
@@ -779,6 +875,52 @@ async function startServer(config, values) {
779
875
  }
780
876
  },
781
877
  });
878
+ // Wire TriageJudge — uses the agent's configured LLM provider
879
+ const triageProvider = agent.config.llmConfig?.modelMode === 'custom'
880
+ ? agent.config.llmConfig.primary : undefined;
881
+ agent.getAttentionController().setTriageJudge(async (prompt) => {
882
+ const response = await llmRouter.chat({
883
+ messages: [
884
+ { role: 'system', content: 'You are a mailbox triage assistant. Output ONLY a single JSON object — no explanation, no markdown fences, no <think> tags. Start your response with {' },
885
+ { role: 'user', content: prompt },
886
+ ],
887
+ temperature: TRIAGE_TEMPERATURE,
888
+ maxTokens: TRIAGE_MAX_TOKENS,
889
+ }, triageProvider);
890
+ return response.content;
891
+ });
892
+ // Wire triage chat function (for mini tool loop during triage)
893
+ agent.getAttentionController().setTriageChatFn(async (messages, tools) => {
894
+ const llmTools = tools?.map(t => ({
895
+ name: t.name,
896
+ description: t.description,
897
+ inputSchema: t.inputSchema,
898
+ }));
899
+ const response = await llmRouter.chat({
900
+ messages: messages,
901
+ tools: llmTools,
902
+ temperature: TRIAGE_TEMPERATURE,
903
+ maxTokens: TRIAGE_MAX_TOKENS,
904
+ }, triageProvider);
905
+ return { content: response.content, toolCalls: response.toolCalls };
906
+ });
907
+ // Wire read-only triage tools from the agent's tool set
908
+ const triageToolMap = new Map();
909
+ const agentTools = agent.getTools();
910
+ for (const toolName of TRIAGE_ALLOWED_TOOLS) {
911
+ const handler = agentTools.get(toolName);
912
+ if (handler) {
913
+ triageToolMap.set(toolName, {
914
+ name: handler.name,
915
+ description: handler.description,
916
+ inputSchema: handler.inputSchema,
917
+ execute: handler.execute.bind(handler),
918
+ });
919
+ }
920
+ }
921
+ if (triageToolMap.size > 0) {
922
+ agent.getAttentionController().setTriageTools(triageToolMap);
923
+ }
782
924
  }
783
925
  catch { /* agent not found */ }
784
926
  };
@@ -850,6 +992,10 @@ async function startServer(config, values) {
850
992
  const ws = apiServer.getWSBroadcaster();
851
993
  ws.broadcast({ type: 'agent:focus', payload: event, timestamp: new Date().toISOString() });
852
994
  });
995
+ eventBus.on('attention:triage', (event) => {
996
+ const ws = apiServer.getWSBroadcaster();
997
+ ws.broadcast({ type: 'agent:triage', payload: event, timestamp: new Date().toISOString() });
998
+ });
853
999
  // Wire agent lifecycle events to WS broadcast
854
1000
  eventBus.on('agent:removed', (event) => {
855
1001
  const { agentId } = event;
@@ -992,12 +1138,28 @@ async function startServer(config, values) {
992
1138
  else {
993
1139
  progress.finish(uiUrl);
994
1140
  }
995
- // Start restored agents in background (server is already accepting requests)
996
- orgService.startRestoredAgentsInBackground();
1141
+ // Non-blocking update check runs after startup, never blocks or throws
1142
+ checkForUpdate().then(info => {
1143
+ if (info.updateAvailable) {
1144
+ console.log(`\n \x1b[33m⬆ New version available: v${info.latestVersion} (current: v${info.currentVersion})\x1b[0m`);
1145
+ console.log(` Run \x1b[1mnpm i -g @markus-global/cli\x1b[0m to upgrade\n`);
1146
+ }
1147
+ }).catch(() => { });
1148
+ // Start restored agents in background (server is already accepting requests),
1149
+ // then auto-resume in_progress tasks once all agents are ready.
1150
+ orgService.startRestoredAgentsInBackground().then(async () => {
1151
+ try {
1152
+ await taskService.resumeInProgressTasks();
1153
+ }
1154
+ catch (err) {
1155
+ log.warn('Failed to auto-resume in_progress tasks', { error: String(err) });
1156
+ }
1157
+ });
997
1158
  process.on('SIGINT', () => {
998
1159
  console.error('\nShutting down...');
999
1160
  closeStartupLogger();
1000
1161
  closeRuntimeLogger();
1162
+ archiveService.stop();
1001
1163
  scheduledTaskRunner.stop();
1002
1164
  apiServer.stop();
1003
1165
  agentManager.shutdown()