@markus-global/cli 0.8.5 → 0.9.0-rc.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 (104) hide show
  1. package/dist/commands/model.d.ts.map +1 -1
  2. package/dist/commands/model.js +9 -2
  3. package/dist/commands/model.js.map +1 -1
  4. package/dist/commands/start.d.ts.map +1 -1
  5. package/dist/commands/start.js +227 -181
  6. package/dist/commands/start.js.map +1 -1
  7. package/dist/markus-browser-extension.zip +0 -0
  8. package/dist/markus.mjs +13015 -6404
  9. package/dist/web-ui/assets/ChatHelpers-Cb26xxzl.js +3 -0
  10. package/dist/web-ui/assets/CommentInput-CmSnFGbv.js +1 -0
  11. package/dist/web-ui/assets/ContentRenderer-CFJndxtW.js +82 -0
  12. package/dist/web-ui/assets/Deliverables-CL88tGZv.js +6 -0
  13. package/dist/web-ui/assets/Notifications-BX3Fx5j5.js +1 -0
  14. package/dist/web-ui/assets/Search-YJ4NBHyk.js +1 -0
  15. package/dist/web-ui/assets/Settings-B8xQ3rEN.js +8 -0
  16. package/dist/web-ui/assets/Store-WYrzY0Mr.js +1 -0
  17. package/dist/web-ui/assets/Team-DwRSN0-a.js +39 -0
  18. package/dist/web-ui/assets/Work-BZV40eAE.css +1 -0
  19. package/dist/web-ui/assets/Work-DNGfvBLY.js +11 -0
  20. package/dist/web-ui/assets/{arc-BdRlL2iO.js → arc-BfS9Mvj7.js} +1 -1
  21. package/dist/web-ui/assets/architectureDiagram-3BPJPVTR-CFNOE9Ft.js +36 -0
  22. package/dist/web-ui/assets/{blockDiagram-GPEHLZMM-BpzalW_a.js → blockDiagram-GPEHLZMM-gD6Hylng.js} +2 -2
  23. package/dist/web-ui/assets/{c4Diagram-AAUBKEIU-BCAI0V-n.js → c4Diagram-AAUBKEIU-8QoBVsn5.js} +2 -2
  24. package/dist/web-ui/assets/channel-1ow_lsxl.js +1 -0
  25. package/dist/web-ui/assets/{chunk-2J33WTMH-Dg7rYV2F.js → chunk-2J33WTMH--5nv1yIK.js} +1 -1
  26. package/dist/web-ui/assets/{chunk-4BX2VUAB-NjIIklhn.js → chunk-4BX2VUAB-DMHNCjRo.js} +1 -1
  27. package/dist/web-ui/assets/chunk-55IACEB6-DoagUtYg.js +1 -0
  28. package/dist/web-ui/assets/{chunk-727SXJPM-D4yVMcHy.js → chunk-727SXJPM-sOd6G91V.js} +1 -1
  29. package/dist/web-ui/assets/{chunk-AQP2D5EJ-BZ2R83hR.js → chunk-AQP2D5EJ-BA1hEJE7.js} +1 -1
  30. package/dist/web-ui/assets/{chunk-FMBD7UC4-dKKoHR7V.js → chunk-FMBD7UC4-D-4YgaFe.js} +1 -1
  31. package/dist/web-ui/assets/chunk-ND2GUHAM-CaESAIrE.js +1 -0
  32. package/dist/web-ui/assets/{chunk-QZHKN3VN-QK1OECHO.js → chunk-QZHKN3VN-k2TrAqxw.js} +1 -1
  33. package/dist/web-ui/assets/classDiagram-4FO5ZUOK-zJLmwj73.js +1 -0
  34. package/dist/web-ui/assets/classDiagram-v2-Q7XG4LA2-zJLmwj73.js +1 -0
  35. package/dist/web-ui/assets/cose-bilkent-S5V4N54A-BWzmZrwb.js +1 -0
  36. package/dist/web-ui/assets/dagre-BM42HDAG-DESAnnYv.js +4 -0
  37. package/dist/web-ui/assets/diagram-2AECGRRQ-BYqdvN4F.js +43 -0
  38. package/dist/web-ui/assets/diagram-5GNKFQAL-CjpQ4xfS.js +10 -0
  39. package/dist/web-ui/assets/{diagram-KO2AKTUF-BAPzH4PL.js → diagram-KO2AKTUF-CfsDLuOy.js} +2 -2
  40. package/dist/web-ui/assets/diagram-LMA3HP47-BZdG-8fy.js +24 -0
  41. package/dist/web-ui/assets/diagram-OG6HWLK6-jCwDnemx.js +24 -0
  42. package/dist/web-ui/assets/{erDiagram-TEJ5UH35-C5lPAlAw.js → erDiagram-TEJ5UH35-BwJwkebk.js} +7 -7
  43. package/dist/web-ui/assets/{flowDiagram-I6XJVG4X-B3yPTMtT.js → flowDiagram-I6XJVG4X-DbFMgK3R.js} +2 -2
  44. package/dist/web-ui/assets/ganttDiagram-6RSMTGT7-BxVoytl3.js +292 -0
  45. package/dist/web-ui/assets/gitGraphDiagram-PVQCEYII-CEgYJOJY.js +106 -0
  46. package/dist/web-ui/assets/index-D_HH4ju2.js +666 -0
  47. package/dist/web-ui/assets/index-YDpknaqo.css +1 -0
  48. package/dist/web-ui/assets/infoDiagram-5YYISTIA-DQWzABtc.js +2 -0
  49. package/dist/web-ui/assets/{ishikawaDiagram-YF4QCWOH-BRM-2a0M.js → ishikawaDiagram-YF4QCWOH-DzmIWtTU.js} +2 -2
  50. package/dist/web-ui/assets/{journeyDiagram-JHISSGLW-ETvNwLl4.js → journeyDiagram-JHISSGLW-BoiaqjiH.js} +2 -2
  51. package/dist/web-ui/assets/{kanban-definition-UN3LZRKU-D066SbN9.js → kanban-definition-UN3LZRKU-CZeB8ThH.js} +6 -6
  52. package/dist/web-ui/assets/linear-D1g5s_fh.js +1 -0
  53. package/dist/web-ui/assets/{mermaid.core-rPZMstL0.js → mermaid.core-B_8QQ1RU.js} +10 -10
  54. package/dist/web-ui/assets/{mindmap-definition-RKZ34NQL-BKYYirSK.js → mindmap-definition-RKZ34NQL-DFF-GTkZ.js} +2 -2
  55. package/dist/web-ui/assets/pieDiagram-4H26LBE5-45MXMyT3.js +30 -0
  56. package/dist/web-ui/assets/{quadrantDiagram-W4KKPZXB-V5VbcvJY.js → quadrantDiagram-W4KKPZXB-kRCd2Nqe.js} +2 -2
  57. package/dist/web-ui/assets/{requirementDiagram-4Y6WPE33-D2uVfqfP.js → requirementDiagram-4Y6WPE33-DSILTvsw.js} +2 -2
  58. package/dist/web-ui/assets/{sankeyDiagram-5OEKKPKP-C8ejeGTc.js → sankeyDiagram-5OEKKPKP-CwkyEhye.js} +2 -2
  59. package/dist/web-ui/assets/{sequenceDiagram-3UESZ5HK-CI3unFup.js → sequenceDiagram-3UESZ5HK-0fo5Z81y.js} +2 -2
  60. package/dist/web-ui/assets/stateDiagram-AJRCARHV-B74U4HO9.js +1 -0
  61. package/dist/web-ui/assets/stateDiagram-v2-BHNVJYJU-bSRzYPvh.js +1 -0
  62. package/dist/web-ui/assets/{timeline-definition-PNZ67QCA-BrkWr53g.js → timeline-definition-PNZ67QCA-mRhWzQjp.js} +2 -2
  63. package/dist/web-ui/assets/transform-CpIVI4vC.js +1 -0
  64. package/dist/web-ui/assets/value-DIujSiJ7.js +1 -0
  65. package/dist/web-ui/assets/{vennDiagram-CIIHVFJN-C8mr8EAq.js → vennDiagram-CIIHVFJN-C82YisLk.js} +2 -2
  66. package/dist/web-ui/assets/{wardley-L42UT6IY-BFlL89Fu.js → wardley-L42UT6IY-Ba6AG0iH.js} +1 -1
  67. package/dist/web-ui/assets/{wardleyDiagram-YWT4CUSO-CezrtcCI.js → wardleyDiagram-YWT4CUSO-BSmLAatg.js} +2 -2
  68. package/dist/web-ui/assets/{xychartDiagram-2RQKCTM6-DrvIX-i_.js → xychartDiagram-2RQKCTM6-Z72DDYUU.js} +2 -2
  69. package/dist/web-ui/index.html +2 -2
  70. package/package.json +1 -1
  71. package/templates/roles/SHARED.md +11 -4
  72. package/templates/roles/agent-father/ROLE.md +1 -1
  73. package/templates/roles/secretary/HEARTBEAT.md +2 -2
  74. package/templates/roles/secretary/ROLE.md +26 -23
  75. package/templates/roles/skill-architect/ROLE.md +2 -2
  76. package/templates/roles/team-factory/ROLE.md +1 -1
  77. package/templates/skills/agent-building/SKILL.md +29 -10
  78. package/templates/skills/agent-building/skill.json +1 -1
  79. package/templates/skills/markus-hub-connector/server.mjs +12 -1
  80. package/templates/skills/skill-building/SKILL.md +30 -10
  81. package/templates/skills/skill-building/skill.json +1 -1
  82. package/templates/skills/team-building/SKILL.md +32 -13
  83. package/templates/skills/team-building/skill.json +1 -1
  84. package/dist/web-ui/assets/architectureDiagram-3BPJPVTR-D0tUssz3.js +0 -36
  85. package/dist/web-ui/assets/channel-CVrfB_bR.js +0 -1
  86. package/dist/web-ui/assets/chunk-55IACEB6-D1NbvEGQ.js +0 -1
  87. package/dist/web-ui/assets/chunk-ND2GUHAM-8DcNh3kD.js +0 -1
  88. package/dist/web-ui/assets/classDiagram-4FO5ZUOK-vzqgiM0p.js +0 -1
  89. package/dist/web-ui/assets/classDiagram-v2-Q7XG4LA2-vzqgiM0p.js +0 -1
  90. package/dist/web-ui/assets/cose-bilkent-S5V4N54A-70zGiErc.js +0 -1
  91. package/dist/web-ui/assets/dagre-BM42HDAG-CjH95T53.js +0 -4
  92. package/dist/web-ui/assets/diagram-2AECGRRQ-D1h-kJfb.js +0 -43
  93. package/dist/web-ui/assets/diagram-5GNKFQAL-BfpyuUIG.js +0 -10
  94. package/dist/web-ui/assets/diagram-LMA3HP47-BzZcoqw9.js +0 -24
  95. package/dist/web-ui/assets/diagram-OG6HWLK6-UzfnxP-8.js +0 -24
  96. package/dist/web-ui/assets/ganttDiagram-6RSMTGT7-M0LiSJYB.js +0 -292
  97. package/dist/web-ui/assets/gitGraphDiagram-PVQCEYII-DMJ8bJ-V.js +0 -106
  98. package/dist/web-ui/assets/index-C0TJwd4k.js +0 -776
  99. package/dist/web-ui/assets/index-C1_HX8vL.css +0 -1
  100. package/dist/web-ui/assets/infoDiagram-5YYISTIA-H3O_dXaX.js +0 -2
  101. package/dist/web-ui/assets/linear-w-ZA_ecm.js +0 -1
  102. package/dist/web-ui/assets/pieDiagram-4H26LBE5-Cwjk7qIl.js +0 -30
  103. package/dist/web-ui/assets/stateDiagram-AJRCARHV-C4edfdOw.js +0 -1
  104. package/dist/web-ui/assets/stateDiagram-v2-BHNVJYJU-CzloKUKx.js +0 -1
@@ -3,7 +3,7 @@ import { existsSync, readFileSync } from 'node:fs';
3
3
  import { homedir } from 'node:os';
4
4
  import { createRequire } from 'node:module';
5
5
  import { allTemplateDirs, resolveTemplatesDir, resolveWebUiDir } from '../paths.js';
6
- import { loadConfig, getDefaultConfigPath, createLogger, closeRuntimeLogger, checkForUpdate, userId, TRIAGE_MAX_TOKENS, TRIAGE_TEMPERATURE, TRIAGE_ALLOWED_TOOLS, } from '@markus/shared';
6
+ import { loadConfig, saveConfig, getDefaultConfigPath, createLogger, closeRuntimeLogger, checkForUpdate, userId, PROVIDERS, } from '@markus/shared';
7
7
  import { AgentManager, LLMRouter, LLMLogger, ModelCatalogService, RoleLoader, createDefaultSkillRegistry, ExternalAgentGateway, } from '@markus/core';
8
8
  import { OrganizationService, TaskService, APIServer, HITLService, BillingService, LicenseService, TelemetryService, AuditService, ProjectService, RequirementService, KnowledgeService, FileKnowledgeStore, DeliverableService, ReportService, TrustService, ArchiveService, StaleDetector, ScheduledTaskRunner, initStorage, searchRegistries, installSkill, WorkflowService, WorkflowRunner, WorkflowScheduler, } from '@markus/org-manager';
9
9
  import { MessageRouter, FeishuAdapter, WebUIAdapter } from '@markus/comms';
@@ -40,128 +40,134 @@ export async function createServices(config) {
40
40
  const providerConfigs = {};
41
41
  let defaultProvider = config.llm.defaultProvider;
42
42
  const llmTimeoutMs = Number(process.env['LLM_TIMEOUT_MS']) || config.llm.timeoutMs || undefined;
43
- const anthropicKey = config.llm.providers['anthropic']?.apiKey ?? process.env['ANTHROPIC_API_KEY'];
44
- if (anthropicKey) {
45
- providerConfigs['anthropic'] = {
46
- provider: 'anthropic',
47
- model: config.llm.providers['anthropic']?.model ?? config.llm.defaultModel,
48
- apiKey: anthropicKey,
49
- timeoutMs: llmTimeoutMs,
50
- };
51
- }
52
- const openaiKey = config.llm.providers['openai']?.apiKey ?? process.env['OPENAI_API_KEY'];
53
- if (openaiKey) {
54
- providerConfigs['openai'] = {
55
- provider: 'openai',
56
- model: config.llm.providers['openai']?.model ?? process.env['OPENAI_MODEL'] ?? 'gpt-5.4',
57
- apiKey: openaiKey,
58
- timeoutMs: llmTimeoutMs,
59
- };
60
- }
61
- const siliconflowKey = config.llm.providers['siliconflow']?.apiKey ?? process.env['SILICONFLOW_API_KEY'];
62
- if (siliconflowKey) {
63
- providerConfigs['siliconflow'] = {
64
- provider: 'siliconflow',
65
- model: config.llm.providers['siliconflow']?.model ?? process.env['SILICONFLOW_MODEL'] ?? 'Qwen/Qwen3.5-35B-A3B',
66
- apiKey: siliconflowKey,
67
- baseUrl: config.llm.providers['siliconflow']?.baseUrl ??
68
- process.env['SILICONFLOW_BASE_URL'] ??
69
- 'https://api.siliconflow.cn/v1',
70
- timeoutMs: llmTimeoutMs,
71
- };
72
- if (config.llm.defaultProvider === 'siliconflow') {
73
- defaultProvider = 'siliconflow';
43
+ const providerMeta = new Map(PROVIDERS.map((p) => [p.id, p]));
44
+ const envPrefix = (id) => id.toUpperCase().replace(/-/g, '_');
45
+ // Markus Cloud — OpenRouter member key only (chat + search).
46
+ const markusCfg = config.llm.providers['markus'] ?? {};
47
+ const markusOrKey = markusCfg.apiKey?.startsWith('sk-or-')
48
+ ? markusCfg.apiKey
49
+ : (markusCfg.apiKey && !/^markus[_-]/i.test(markusCfg.apiKey) ? markusCfg.apiKey : '');
50
+ if (markusOrKey) {
51
+ const cfgBaseUrl = markusCfg.baseUrl ?? '';
52
+ const markusOrBase = (cfgBaseUrl && !/localhost:8787|127\.0\.0\.1:8787|workers\.dev|markus-proxy/i.test(cfgBaseUrl))
53
+ ? cfgBaseUrl
54
+ : 'https://openrouter.ai/api/v1';
55
+ let markusModelsUrl = markusCfg.modelsUrl ?? process.env['MARKUS_MODELS_URL'] ?? '';
56
+ const hubBase = (config.hub?.url || process.env['MARKUS_HUB_URL'] || '').replace(/\/+$/, '');
57
+ if (!markusModelsUrl && hubBase) {
58
+ markusModelsUrl = `${hubBase}/api/models/live/markus`;
74
59
  }
75
- }
76
- const siliconflowIntlKey = config.llm.providers['siliconflow-intl']?.apiKey ?? process.env['SILICONFLOW_INTL_API_KEY'];
77
- if (siliconflowIntlKey) {
78
- providerConfigs['siliconflow-intl'] = {
79
- provider: 'siliconflow',
80
- model: config.llm.providers['siliconflow-intl']?.model ?? process.env['SILICONFLOW_INTL_MODEL'] ?? 'Qwen/Qwen3.5-35B-A3B',
81
- apiKey: siliconflowIntlKey,
82
- baseUrl: config.llm.providers['siliconflow-intl']?.baseUrl ??
83
- process.env['SILICONFLOW_INTL_BASE_URL'] ??
84
- 'https://api-st.siliconflow.cn/v1',
85
- timeoutMs: llmTimeoutMs,
86
- };
87
- if (config.llm.defaultProvider === 'siliconflow-intl') {
88
- defaultProvider = 'siliconflow-intl';
60
+ if (markusModelsUrl && hubBase) {
61
+ try {
62
+ const mu = new URL(markusModelsUrl);
63
+ const hu = new URL(hubBase.includes('://') ? hubBase : `http://${hubBase}`);
64
+ if (mu.host !== hu.host) {
65
+ markusModelsUrl = `${hubBase}/api/models/live/markus`;
66
+ }
67
+ }
68
+ catch { /* keep */ }
89
69
  }
90
- }
91
- const minimaxKey = config.llm.providers['minimax']?.apiKey ?? process.env['MINIMAX_API_KEY'];
92
- if (minimaxKey) {
93
- providerConfigs['minimax'] = {
94
- provider: 'openai',
95
- model: config.llm.providers['minimax']?.model ?? process.env['MINIMAX_MODEL'] ?? 'MiniMax-M3',
96
- apiKey: minimaxKey,
97
- baseUrl: config.llm.providers['minimax']?.baseUrl ??
98
- process.env['MINIMAX_BASE_URL'] ??
99
- 'https://api.minimax.io/v1',
100
- timeoutMs: llmTimeoutMs,
101
- };
102
- if (config.llm.defaultProvider === 'minimax') {
103
- defaultProvider = 'minimax';
70
+ const prev = markusCfg;
71
+ const needPersist = (markusModelsUrl && markusModelsUrl !== (prev.modelsUrl ?? ''))
72
+ || (markusOrBase && markusOrBase !== (prev.baseUrl ?? ''))
73
+ || (markusOrKey && markusOrKey !== (prev.apiKey ?? ''))
74
+ || !!(prev.subscriptionKey || prev.proxyUrl);
75
+ if (needPersist) {
76
+ try {
77
+ saveConfig({
78
+ llm: {
79
+ providers: {
80
+ markus: {
81
+ apiKey: markusOrKey,
82
+ baseUrl: markusOrBase,
83
+ ...(markusModelsUrl ? { modelsUrl: markusModelsUrl } : {}),
84
+ ...(prev.model ? { model: prev.model } : {}),
85
+ },
86
+ },
87
+ },
88
+ });
89
+ log.info('Persisted Markus OpenRouter credentials', {
90
+ modelsUrl: markusModelsUrl,
91
+ baseUrl: markusOrBase,
92
+ hasOrKey: true,
93
+ });
94
+ }
95
+ catch (err) {
96
+ log.warn('Failed to persist Markus config', { error: String(err) });
97
+ }
104
98
  }
105
- }
106
- const minimaxCnKey = config.llm.providers['minimax-cn']?.apiKey ?? process.env['MINIMAX_CN_API_KEY'];
107
- if (minimaxCnKey) {
108
- providerConfigs['minimax-cn'] = {
109
- provider: 'openai',
110
- model: config.llm.providers['minimax-cn']?.model ?? process.env['MINIMAX_CN_MODEL'] ?? 'MiniMax-M3',
111
- apiKey: minimaxCnKey,
112
- baseUrl: config.llm.providers['minimax-cn']?.baseUrl ??
113
- process.env['MINIMAX_CN_BASE_URL'] ??
114
- 'https://api.minimaxi.com/v1',
115
- timeoutMs: llmTimeoutMs,
116
- };
117
- if (config.llm.defaultProvider === 'minimax-cn') {
118
- defaultProvider = 'minimax-cn';
99
+ process.env['MARKUS_OPENROUTER_KEY'] = markusOrKey;
100
+ process.env['MARKUS_OPENROUTER_BASE'] = markusOrBase;
101
+ delete process.env['MARKUS_SUBSCRIPTION_KEY'];
102
+ delete process.env['MARKUS_PROXY_URL'];
103
+ delete process.env['MARKUS_SEARCH_URL'];
104
+ if (markusModelsUrl && !process.env['MARKUS_MODELS_URL']) {
105
+ process.env['MARKUS_MODELS_URL'] = markusModelsUrl;
119
106
  }
120
- }
121
- const openrouterKey = config.llm.providers['openrouter']?.apiKey ?? process.env['OPENROUTER_API_KEY'];
122
- if (openrouterKey) {
123
- providerConfigs['openrouter'] = {
124
- provider: 'openrouter',
125
- model: config.llm.providers['openrouter']?.model ?? process.env['OPENROUTER_MODEL'] ?? 'xiaomi/mimo-v2-pro',
126
- apiKey: openrouterKey,
127
- baseUrl: config.llm.providers['openrouter']?.baseUrl ??
128
- process.env['OPENROUTER_BASE_URL'] ??
129
- 'https://openrouter.ai/api/v1',
107
+ providerConfigs['markus'] = {
108
+ provider: 'markus',
109
+ model: markusCfg.model ?? '',
110
+ apiKey: markusOrKey,
111
+ baseUrl: markusOrBase,
112
+ ...(markusModelsUrl ? { modelsUrl: markusModelsUrl } : {}),
130
113
  timeoutMs: llmTimeoutMs,
131
114
  };
132
- if (config.llm.defaultProvider === 'openrouter') {
133
- defaultProvider = 'openrouter';
134
- }
135
115
  }
136
- const zaiKey = config.llm.providers['zai']?.apiKey ?? process.env['ZAI_API_KEY'];
137
- if (zaiKey) {
138
- providerConfigs['zai'] = {
139
- provider: 'zai',
140
- model: config.llm.providers['zai']?.model ?? process.env['ZAI_MODEL'] ?? 'glm-5.1',
141
- apiKey: zaiKey,
142
- baseUrl: config.llm.providers['zai']?.baseUrl ??
143
- process.env['ZAI_BASE_URL'] ??
144
- 'https://api.z.ai/api/paas/v4',
145
- timeoutMs: llmTimeoutMs,
146
- };
147
- if (config.llm.defaultProvider === 'zai') {
148
- defaultProvider = 'zai';
116
+ else if (config.llm.defaultProvider === 'markus') {
117
+ log.warn('Markus Cloud AI is set as the default provider but no OpenRouter member key is configured. ' +
118
+ 'Sign in to Markus Hub to obtain credentials.');
119
+ }
120
+ // Load every provider from markus.json (+ env fallbacks). Previously only a
121
+ // hardcoded subset was wired on boot, so Google/Ollama/DashScope/custom
122
+ // providers saved via Settings vanished after restart (#244).
123
+ const candidateNames = new Set([
124
+ ...Object.keys(config.llm.providers ?? {}),
125
+ ...PROVIDERS.map((p) => p.id),
126
+ ]);
127
+ candidateNames.delete('markus');
128
+ for (const name of candidateNames) {
129
+ const cfg = config.llm.providers[name] ?? {};
130
+ const meta = providerMeta.get(name);
131
+ const prefix = envPrefix(name);
132
+ if (name === 'ollama') {
133
+ const baseUrl = cfg.baseUrl
134
+ ?? process.env['OLLAMA_BASE_URL']
135
+ ?? meta?.baseUrl;
136
+ const model = cfg.model
137
+ ?? process.env['OLLAMA_MODEL']
138
+ ?? meta?.defaultModel
139
+ ?? 'llama3';
140
+ if (baseUrl || cfg.model || process.env['OLLAMA_BASE_URL']) {
141
+ providerConfigs['ollama'] = {
142
+ provider: 'ollama',
143
+ model,
144
+ ...(baseUrl ? { baseUrl } : {}),
145
+ timeoutMs: llmTimeoutMs,
146
+ };
147
+ }
148
+ continue;
149
149
  }
150
- }
151
- const deepseekKey = config.llm.providers['deepseek']?.apiKey ?? process.env['DEEPSEEK_API_KEY'];
152
- if (deepseekKey) {
153
- providerConfigs['deepseek'] = {
154
- provider: 'deepseek',
155
- model: config.llm.providers['deepseek']?.model ?? process.env['DEEPSEEK_MODEL'] ?? 'deepseek-v4-flash',
156
- apiKey: deepseekKey,
157
- baseUrl: config.llm.providers['deepseek']?.baseUrl ??
158
- process.env['DEEPSEEK_BASE_URL'] ??
159
- 'https://api.deepseek.com',
150
+ const apiKey = cfg.apiKey
151
+ ?? (meta ? process.env[meta.envKey] : undefined)
152
+ ?? process.env[`${prefix}_API_KEY`];
153
+ if (!apiKey)
154
+ continue;
155
+ const model = cfg.model
156
+ ?? process.env[`${prefix}_MODEL`]
157
+ ?? meta?.defaultModel
158
+ ?? (name === 'anthropic' ? config.llm.defaultModel : undefined)
159
+ ?? '';
160
+ const baseUrl = cfg.baseUrl
161
+ ?? process.env[`${prefix}_BASE_URL`]
162
+ ?? meta?.baseUrl;
163
+ providerConfigs[name] = {
164
+ // Name may be a known LLMProvider or a custom OpenAI-compatible id.
165
+ provider: name,
166
+ model,
167
+ apiKey,
168
+ ...(baseUrl ? { baseUrl } : {}),
160
169
  timeoutMs: llmTimeoutMs,
161
170
  };
162
- if (config.llm.defaultProvider === 'deepseek') {
163
- defaultProvider = 'deepseek';
164
- }
165
171
  }
166
172
  // If the configured default provider has no API key, fall back to the first available one
167
173
  if (!providerConfigs[defaultProvider]) {
@@ -474,6 +480,20 @@ async function startServerCore(config, values, opts) {
474
480
  if (config.integrations?.search?.bochaApiKey && !process.env['BOCHA_API_KEY']) {
475
481
  process.env['BOCHA_API_KEY'] = config.integrations.search.bochaApiKey;
476
482
  }
483
+ // Markus-hosted search (OpenRouter member key): opt-out + BYOK disable list.
484
+ if (config.integrations?.search?.useMarkusHosted === false) {
485
+ process.env['MARKUS_SEARCH_ENABLED'] = '0';
486
+ }
487
+ if (config.integrations?.search?.markusSearchModel) {
488
+ process.env['MARKUS_SEARCH_OR_MODEL'] = config.integrations.search.markusSearchModel;
489
+ }
490
+ {
491
+ const s = config.integrations?.search ?? {};
492
+ const disabled = ['serper', 'tavily', 'bing', 'google', 'serpapi', 'brave', 'exa', 'bocha']
493
+ .filter(id => s[`${id}Enabled`] === false);
494
+ if (disabled.length)
495
+ process.env['SEARCH_DISABLED_PROVIDERS'] = disabled.join(',');
496
+ }
477
497
  const apiPort = Number(values['port']) || config.server.apiPort;
478
498
  const { orgService, taskService, agentManager, llmRouter, skillRegistry, hitlService, billingService, licenseService, telemetryService, auditService, bootstrapOwnerId, } = await createServices(config);
479
499
  progress?.complete(3, 'SQLite storage initialised');
@@ -673,17 +693,40 @@ async function startServerCore(config, values, opts) {
673
693
  if (builtinTeamsDir && existsSync(builtinTeamsDir)) {
674
694
  builderService.setBuiltinTeamTemplatesDir(builtinTeamsDir);
675
695
  }
696
+ builderService.ensureArtifactDirs();
676
697
  agentManager.setBuilderService(builderService);
677
698
  }
678
699
  const hubClient = apiServer.getHubClient();
679
700
  if (hubClient) {
680
701
  agentManager.setHubClient(hubClient);
681
702
  }
682
- // Wire team/agent update callbacks for manager tools
703
+ // Wire team/agent update callbacks for manager + secretary tools
683
704
  agentManager.setTeamUpdater(async (teamId, data) => {
684
705
  const team = await orgService.updateTeam(teamId, data);
685
706
  return { id: teamId, name: team.name, description: team.description };
686
707
  });
708
+ agentManager.setTeamCreator(async (orgId, name, description) => {
709
+ const team = await orgService.createTeam(orgId, name, description);
710
+ // Same event POST /api/teams emits — Team Chat sidebar refreshes without a full page reload.
711
+ apiServer.getWSBroadcaster()?.broadcast({
712
+ type: 'chat:group_created',
713
+ payload: {
714
+ chatId: `group:${team.id}`,
715
+ name: team.name,
716
+ creatorId: '',
717
+ creatorName: '',
718
+ teamId: team.id,
719
+ },
720
+ timestamp: new Date().toISOString(),
721
+ });
722
+ return { id: team.id, name: team.name, description: team.description };
723
+ });
724
+ agentManager.setOrgTeamLister((orgId) => orgService.listTeams(orgId).map(t => ({
725
+ id: t.id,
726
+ name: t.name,
727
+ description: t.description,
728
+ memberAgentIds: t.memberAgentIds,
729
+ })));
687
730
  if (storage) {
688
731
  agentManager.setAgentConfigPersister(async (agentId, data) => {
689
732
  await storage.agentRepo.updateConfig(agentId, data);
@@ -717,6 +760,7 @@ async function startServerCore(config, values, opts) {
717
760
  targetUserId: ownerUserId,
718
761
  options: opts.options,
719
762
  allowFreeform: opts.allowFreeform,
763
+ questions: opts.questions,
720
764
  details: { priority: opts.priority, taskId: opts.relatedTaskId, taskTitle },
721
765
  });
722
766
  });
@@ -733,6 +777,16 @@ async function startServerCore(config, values, opts) {
733
777
  metadata: opts.metadata,
734
778
  });
735
779
  });
780
+ // Seed autonomous-run locale/timezone from the owner's stored preferences.
781
+ try {
782
+ const ownerIdentity = orgService.resolveHumanIdentity(ownerUserId);
783
+ if (ownerIdentity && (ownerIdentity.locale || ownerIdentity.timezone)) {
784
+ agentManager.setRuntimeViewerContext({ locale: ownerIdentity.locale, timezone: ownerIdentity.timezone });
785
+ }
786
+ }
787
+ catch (e) {
788
+ log.warn('Failed to seed runtime viewer context from owner preferences', { error: String(e) });
789
+ }
736
790
  // Ensure every agent has a main session on startup, then persist activity logs to it
737
791
  if (storage?.chatSessionRepo) {
738
792
  // Migrate legacy assistant messages that lack segments metadata
@@ -795,6 +849,42 @@ async function startServerCore(config, values, opts) {
795
849
  log.warn('Failed to persist activity log', { agentId, error: String(e) });
796
850
  }
797
851
  });
852
+ // Heartbeat interval changed (by an agent via set_heartbeat_interval, or any
853
+ // in-process update): persist the new value so it survives restart.
854
+ agentManager.getEventBus().on('agent:heartbeat-interval-changed', async (evt) => {
855
+ const { agentId, intervalMs } = evt;
856
+ try {
857
+ await storage.agentRepo.updateConfig(agentId, { heartbeatIntervalMs: intervalMs });
858
+ }
859
+ catch (e) {
860
+ log.warn('Failed to persist heartbeat interval change', { agentId, error: String(e) });
861
+ }
862
+ });
863
+ // Team Chat right-panel open/collapse — deliver to the interacting user's UI.
864
+ agentManager.getEventBus().on('agent:ui-layout', (evt) => {
865
+ const event = evt;
866
+ try {
867
+ const payload = {
868
+ agentId: event.agentId,
869
+ action: event.action,
870
+ ...(event.panel ? { panel: event.panel } : {}),
871
+ };
872
+ const wsEvent = {
873
+ type: 'ui:right_panel',
874
+ payload,
875
+ timestamp: new Date().toISOString(),
876
+ };
877
+ if (event.targetUserId) {
878
+ ws.sendToUser(event.targetUserId, wsEvent);
879
+ }
880
+ else {
881
+ ws.broadcast(wsEvent);
882
+ }
883
+ }
884
+ catch (e) {
885
+ log.warn('Failed to broadcast ui-layout event', { agentId: event.agentId, error: String(e) });
886
+ }
887
+ });
798
888
  // notify_user: persist as regular chat message + WS broadcast + notification bell
799
889
  agentManager.getEventBus().on('agent:notify-user', async (evt) => {
800
890
  const { agentId, title, body, priority, taskId, requirementId, targetUserId } = evt;
@@ -1061,15 +1151,30 @@ async function startServerCore(config, values, opts) {
1061
1151
  durationMs: event.durationMs,
1062
1152
  success: event.success,
1063
1153
  detail: event.detail,
1154
+ metadata: {
1155
+ cost: event.cost ?? 0,
1156
+ cuCost: event.cuCost,
1157
+ provider: event.provider,
1158
+ inputTokens: event.inputTokens,
1159
+ outputTokens: event.outputTokens,
1160
+ },
1064
1161
  });
1065
1162
  if (event.tokensUsed && event.type === 'llm_request') {
1066
- auditService.recordLLMUsage('default', agentId, Math.floor(event.tokensUsed * 0.7), Math.ceil(event.tokensUsed * 0.3));
1163
+ const inputTok = event.inputTokens ?? Math.floor(event.tokensUsed * 0.7);
1164
+ const outputTok = event.outputTokens ?? Math.ceil(event.tokensUsed * 0.3);
1165
+ auditService.recordLLMUsage('default', agentId, inputTok, outputTok);
1067
1166
  billingService.recordUsage({
1068
1167
  orgId: 'default',
1069
1168
  agentId,
1070
1169
  type: 'llm_tokens',
1071
1170
  amount: event.tokensUsed,
1072
- metadata: { action: event.action },
1171
+ metadata: {
1172
+ action: event.action,
1173
+ cuCost: event.cuCost,
1174
+ provider: event.provider,
1175
+ inputTokens: event.inputTokens,
1176
+ outputTokens: event.outputTokens,
1177
+ },
1073
1178
  });
1074
1179
  }
1075
1180
  if (event.type === 'tool_call') {
@@ -1082,17 +1187,6 @@ async function startServerCore(config, values, opts) {
1082
1187
  });
1083
1188
  }
1084
1189
  });
1085
- billingService.setToolCallsTodayProvider(() => {
1086
- let total = 0;
1087
- for (const a of agentManager.listAgents()) {
1088
- try {
1089
- total += agentManager.getAgent(a.id).getUsageStats().toolCallsToday;
1090
- }
1091
- catch { /* not loaded */ }
1092
- }
1093
- return total;
1094
- });
1095
- agentManager.setToolCallLimitChecker(() => billingService.checkLimit('default', 'tool_call'));
1096
1190
  // Wire agent state changes to DB persistence + WS broadcast
1097
1191
  if (storage) {
1098
1192
  agentManager.setStateChangeHandler(async (agentId, state) => {
@@ -1282,27 +1376,11 @@ async function startServerCore(config, values, opts) {
1282
1376
  }
1283
1377
  },
1284
1378
  });
1285
- // Wire TriageJudge — used as fallback when full-session deliberation
1286
- // (delegate.performDeliberation) is not triggered or returns null.
1287
- const triageProvider = agent.config.llmConfig?.modelMode === 'custom'
1288
- ? agent.config.llmConfig.primary : undefined;
1289
- const triageSystemPrompt = 'You are a mailbox triage assistant. Output ONLY a single JSON object — no explanation, no markdown fences, no <think> tags. Start your response with {';
1290
- const triageCacheSegments = [{ content: triageSystemPrompt, cacheBreakpoint: true }];
1291
- agent.getAttentionController().setTriageJudge(async (prompt) => {
1292
- const response = await llmRouter.chat({
1293
- messages: [
1294
- { role: 'system', content: triageSystemPrompt },
1295
- { role: 'user', content: prompt },
1296
- ],
1297
- systemCacheSegments: triageCacheSegments,
1298
- temperature: TRIAGE_TEMPERATURE,
1299
- maxTokens: TRIAGE_MAX_TOKENS,
1300
- }, triageProvider);
1301
- return response.content;
1302
- });
1303
1379
  // Wire LLM interrupt judge — evaluates whether to preempt current
1304
1380
  // work when a new message arrives (e.g. "stop publishing", "pause that task").
1305
1381
  // Only called when heuristics return 'continue' for ambiguous cases.
1382
+ const triageProvider = agent.config.llmConfig?.modelMode === 'custom'
1383
+ ? agent.config.llmConfig.primary : undefined;
1306
1384
  const interruptSystemPrompt = 'You are an attention interrupt judge. Decide whether to interrupt current work for a new incoming message. Reply with ONLY one word: continue, preempt, cancel, merge, or defer. Use "preempt" to pause (resume later) and "cancel" to permanently stop current work.';
1307
1385
  const interruptCacheSegments = [{ content: interruptSystemPrompt, cacheBreakpoint: true }];
1308
1386
  agent.getAttentionController().setLLMJudge(async (prompt) => {
@@ -1313,47 +1391,15 @@ async function startServerCore(config, values, opts) {
1313
1391
  ],
1314
1392
  systemCacheSegments: interruptCacheSegments,
1315
1393
  temperature: 0.1,
1316
- maxTokens: 32,
1394
+ // No maxTokens cap: a tight cap (e.g. 32) is fully consumed by a
1395
+ // reasoning model's hidden reasoning, leaving content empty so the
1396
+ // judge always degrades to 'continue'. The router fills the model's
1397
+ // real limit; the one-word answer stops the generation naturally.
1317
1398
  }, triageProvider);
1318
1399
  const raw = response.content.trim().toLowerCase();
1319
1400
  const valid = ['continue', 'preempt', 'cancel', 'merge', 'defer'];
1320
1401
  return valid.includes(raw) ? raw : 'continue';
1321
1402
  });
1322
- // Wire triage chat function (fallback mini tool loop, used when
1323
- // performDeliberation is not available or deliberation is disabled).
1324
- // The attention controller prefers full-session deliberation via
1325
- // delegate.performDeliberation when available.
1326
- agent.getAttentionController().setTriageChatFn(async (messages, tools) => {
1327
- const llmTools = tools?.map(t => ({
1328
- name: t.name,
1329
- description: t.description,
1330
- inputSchema: t.inputSchema,
1331
- }));
1332
- const response = await llmRouter.chat({
1333
- messages: messages,
1334
- tools: llmTools,
1335
- temperature: TRIAGE_TEMPERATURE,
1336
- maxTokens: TRIAGE_MAX_TOKENS,
1337
- }, triageProvider);
1338
- return { content: response.content, toolCalls: response.toolCalls, reasoningContent: response.reasoningContent };
1339
- });
1340
- // Wire read-only triage tools from the agent's tool set
1341
- const triageToolMap = new Map();
1342
- const agentTools = agent.getTools();
1343
- for (const toolName of TRIAGE_ALLOWED_TOOLS) {
1344
- const handler = agentTools.get(toolName);
1345
- if (handler) {
1346
- triageToolMap.set(toolName, {
1347
- name: handler.name,
1348
- description: handler.description,
1349
- inputSchema: handler.inputSchema,
1350
- execute: handler.execute.bind(handler),
1351
- });
1352
- }
1353
- }
1354
- if (triageToolMap.size > 0) {
1355
- agent.getAttentionController().setTriageTools(triageToolMap);
1356
- }
1357
1403
  }
1358
1404
  catch { /* agent not found */ }
1359
1405
  };