@markus-global/cli 0.7.13 → 0.8.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.
package/dist/markus.mjs CHANGED
@@ -3791,6 +3791,34 @@ var init_model_catalog = __esm({
3791
3791
  }
3792
3792
  });
3793
3793
 
3794
+ // ../shared/dist/types/license.js
3795
+ var PLAN_LIMITS, ENTERPRISE_FEATURES;
3796
+ var init_license = __esm({
3797
+ "../shared/dist/types/license.js"() {
3798
+ "use strict";
3799
+ PLAN_LIMITS = {
3800
+ free: {
3801
+ maxTeams: 1,
3802
+ maxToolCallsPerDay: 500,
3803
+ maxUsers: 1
3804
+ },
3805
+ enterprise: {
3806
+ maxTeams: -1,
3807
+ maxToolCallsPerDay: -1,
3808
+ maxUsers: -1
3809
+ }
3810
+ };
3811
+ ENTERPRISE_FEATURES = [
3812
+ "multi_user",
3813
+ "unlimited_teams",
3814
+ "unlimited_tools",
3815
+ "sso",
3816
+ "audit_enhanced",
3817
+ "multi_instance"
3818
+ ];
3819
+ }
3820
+ });
3821
+
3794
3822
  // ../shared/dist/utils/config.js
3795
3823
  import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
3796
3824
  import { resolve, join } from "node:path";
@@ -4515,6 +4543,7 @@ __export(dist_exports, {
4515
4543
  CognitiveDepth: () => CognitiveDepth,
4516
4544
  DELIBERATION_ALLOWED_TOOLS: () => DELIBERATION_ALLOWED_TOOLS,
4517
4545
  DELIVERABLE_TITLE_CHARS: () => DELIVERABLE_TITLE_CHARS,
4546
+ ENTERPRISE_FEATURES: () => ENTERPRISE_FEATURES,
4518
4547
  ENTITY_COMMENTS_DEFAULT: () => ENTITY_COMMENTS_DEFAULT,
4519
4548
  HEARTBEAT_DAILY_LOG_CHARS: () => HEARTBEAT_DAILY_LOG_CHARS,
4520
4549
  HEARTBEAT_MIN_INITIAL_DELAY_MS: () => HEARTBEAT_MIN_INITIAL_DELAY_MS,
@@ -4533,6 +4562,7 @@ __export(dist_exports, {
4533
4562
  MEMORY_MD_SECTION_MAX_CHARS: () => MEMORY_MD_SECTION_MAX_CHARS,
4534
4563
  MEMORY_MD_TOTAL_MAX_CHARS: () => MEMORY_MD_TOTAL_MAX_CHARS,
4535
4564
  MailboxPriorityLevel: () => MailboxPriorityLevel,
4565
+ PLAN_LIMITS: () => PLAN_LIMITS,
4536
4566
  PREEMPT_REQUEUE_DELAY_MS: () => PREEMPT_REQUEUE_DELAY_MS,
4537
4567
  PRIORITY_LABELS: () => PRIORITY_LABELS,
4538
4568
  PROMPT_DEP_DESC_CHARS: () => PROMPT_DEP_DESC_CHARS,
@@ -4653,6 +4683,7 @@ var init_dist = __esm({
4653
4683
  init_mailbox();
4654
4684
  init_cognitive();
4655
4685
  init_model_catalog();
4686
+ init_license();
4656
4687
  init_config();
4657
4688
  init_logger();
4658
4689
  init_id();
@@ -41975,14 +42006,14 @@ var require_turndown_cjs = __commonJS({
41975
42006
  } else if (node.nodeType === 1) {
41976
42007
  replacement = replacementForNode.call(self, node);
41977
42008
  }
41978
- return join33(output, replacement);
42009
+ return join35(output, replacement);
41979
42010
  }, "");
41980
42011
  }
41981
42012
  function postProcess(output) {
41982
42013
  var self = this;
41983
42014
  this.rules.forEach(function(rule) {
41984
42015
  if (typeof rule.append === "function") {
41985
- output = join33(output, rule.append(self.options));
42016
+ output = join35(output, rule.append(self.options));
41986
42017
  }
41987
42018
  });
41988
42019
  return output.replace(/^[\t\r\n]+/, "").replace(/[\t\r\n\s]+$/, "");
@@ -41994,7 +42025,7 @@ var require_turndown_cjs = __commonJS({
41994
42025
  if (whitespace2.leading || whitespace2.trailing) content = content.trim();
41995
42026
  return whitespace2.leading + rule.replacement(content, node, this.options) + whitespace2.trailing;
41996
42027
  }
41997
- function join33(output, replacement) {
42028
+ function join35(output, replacement) {
41998
42029
  var s1 = trimTrailingNewlines(output);
41999
42030
  var s2 = trimLeadingNewlines(replacement);
42000
42031
  var nls = Math.max(output.length - s1.length, replacement.length - s2.length);
@@ -44727,7 +44758,8 @@ var init_attention = __esm({
44727
44758
  }
44728
44759
  await this.processFocusedItem(item);
44729
44760
  } catch (err) {
44730
- log14.error("Attention loop iteration failed \u2014 requeueing item and continuing", {
44761
+ const isUserInteraction = _AttentionController.USER_INTERACTION_TYPES.has(item.sourceType);
44762
+ log14.error(`Attention loop iteration failed \u2014 ${isUserInteraction ? "completing" : "requeueing"} item and continuing`, {
44731
44763
  agentId: this.agentId,
44732
44764
  itemId: item.id,
44733
44765
  type: item.sourceType,
@@ -44738,9 +44770,16 @@ var init_attention = __esm({
44738
44770
  this.interruptSignal = false;
44739
44771
  this.pendingInterruptItem = void 0;
44740
44772
  this.lastYieldDecision = void 0;
44741
- try {
44742
- this.mailbox.requeue(item);
44743
- } catch {
44773
+ if (isUserInteraction) {
44774
+ try {
44775
+ this.mailbox.complete(item.id);
44776
+ } catch {
44777
+ }
44778
+ } else {
44779
+ try {
44780
+ this.mailbox.requeue(item);
44781
+ } catch {
44782
+ }
44744
44783
  }
44745
44784
  }
44746
44785
  } catch (outerErr) {
@@ -44841,15 +44880,33 @@ var init_attention = __esm({
44841
44880
  } else {
44842
44881
  const abnormalReason = detectAbnormalCompletion(reply, item);
44843
44882
  const retries = item.retryCount ?? 0;
44883
+ const isUserInteraction = _AttentionController.USER_INTERACTION_TYPES.has(item.sourceType);
44844
44884
  if (abnormalReason && retries < MAILBOX_ITEM_MAX_RETRIES) {
44845
- log14.warn("Abnormal completion detected, requeueing for retry", {
44846
- agentId: this.agentId,
44847
- itemId: item.id,
44848
- type: item.sourceType,
44849
- retryCount: retries + 1,
44850
- reason: abnormalReason
44851
- });
44852
- this.mailbox.requeue(item);
44885
+ if (abnormalReason === "completion marker missing from reply") {
44886
+ log14.warn("Completion marker still missing after in-session continuation \u2014 completing without retry", {
44887
+ agentId: this.agentId,
44888
+ itemId: item.id,
44889
+ type: item.sourceType
44890
+ });
44891
+ this.mailbox.complete(item.id);
44892
+ } else if (isUserInteraction) {
44893
+ log14.warn("Abnormal completion for user interaction \u2014 completing without retry", {
44894
+ agentId: this.agentId,
44895
+ itemId: item.id,
44896
+ type: item.sourceType,
44897
+ reason: abnormalReason
44898
+ });
44899
+ this.mailbox.complete(item.id);
44900
+ } else {
44901
+ log14.warn("Abnormal completion detected, requeueing for retry", {
44902
+ agentId: this.agentId,
44903
+ itemId: item.id,
44904
+ type: item.sourceType,
44905
+ retryCount: retries + 1,
44906
+ reason: abnormalReason
44907
+ });
44908
+ this.mailbox.requeue(item);
44909
+ }
44853
44910
  } else {
44854
44911
  if (abnormalReason) {
44855
44912
  log14.error("Abnormal completion persisted after max retries, completing anyway", {
@@ -46779,6 +46836,7 @@ var init_agent2 = __esm({
46779
46836
  auditCallback;
46780
46837
  escalationCallback;
46781
46838
  approvalCallback;
46839
+ toolCallLimitChecker;
46782
46840
  tasksFetcher;
46783
46841
  consecutiveFailures = 0;
46784
46842
  metricsCollector;
@@ -47328,6 +47386,57 @@ ${notification.stdoutTail}`);
47328
47386
  }
47329
47387
  };
47330
47388
  }
47389
+ /**
47390
+ * If the reply is missing the required completion marker, inject a
47391
+ * continuation prompt into the existing session and make one more LLM
47392
+ * call to obtain it — instead of letting the attention controller
47393
+ * requeue the entire item from scratch (which duplicates side effects).
47394
+ *
47395
+ * Limited to a single attempt; if the marker is still missing afterwards
47396
+ * the reply is returned as-is and the attention controller will complete
47397
+ * the item without retry.
47398
+ */
47399
+ async ensureCompletionMarker(reply, sessionId) {
47400
+ if (!reply || reply === "[cancelled]" || reply === "[preempted]" || reply === "[merged]")
47401
+ return reply;
47402
+ if (reply.includes(COMPLETION_MARKER))
47403
+ return reply;
47404
+ if (!sessionId || !this.memory.getSession(sessionId))
47405
+ return reply;
47406
+ log17.info("Completion marker missing \u2014 continuing in-session to obtain marker", {
47407
+ agentId: this.id,
47408
+ sessionId,
47409
+ replyLength: reply.length
47410
+ });
47411
+ this.memory.appendMessage(sessionId, {
47412
+ role: "user",
47413
+ content: `[SYSTEM] Your previous response did not include the required completion marker. You MUST end your response with exactly: ${COMPLETION_MARKER}`
47414
+ });
47415
+ try {
47416
+ const sessionMessages = this.memory.getRecentMessages(sessionId, 50);
47417
+ const prepared = await this.contextEngine.prepareMessages({
47418
+ systemPrompt: "You are completing a previous response. Finish any remaining work and end your response with the required completion marker.",
47419
+ sessionMessages,
47420
+ memory: this.memory,
47421
+ sessionId,
47422
+ agentId: this.id,
47423
+ modelContextWindow: this.llmRouter.getModelContextWindow(this.getEffectiveProvider()),
47424
+ modelMaxOutput: this.llmRouter.getModelMaxOutput(this.getEffectiveProvider()),
47425
+ toolDefinitions: []
47426
+ });
47427
+ const continuation = await this.llmRouter.chat({ messages: prepared.messages }, this.getEffectiveProvider());
47428
+ const contReply = continuation.content ?? "";
47429
+ this.memory.appendMessage(sessionId, { role: "assistant", content: contReply });
47430
+ return reply + contReply;
47431
+ } catch (err) {
47432
+ log17.warn("Failed to obtain completion marker via continuation \u2014 returning original reply", {
47433
+ agentId: this.id,
47434
+ sessionId,
47435
+ error: String(err)
47436
+ });
47437
+ return reply;
47438
+ }
47439
+ }
47331
47440
  /**
47332
47441
  * Route a mailbox item to the appropriate Agent processing method.
47333
47442
  * Options like sessionId/images/scenario are forwarded from payload.extra
@@ -47410,7 +47519,9 @@ ${item.payload.content}` + batchSuffix;
47410
47519
  itemId: item.id
47411
47520
  });
47412
47521
  } else {
47413
- const reply2 = await this.handleMessageStream(item.payload.content + markerSuffix, extra.onEvent, item.metadata?.senderId, senderInfo, ct, extra.images, extra.fileNames);
47522
+ let reply2 = await this.handleMessageStream(item.payload.content + markerSuffix, extra.onEvent, item.metadata?.senderId, senderInfo, ct, extra.images, extra.fileNames);
47523
+ if (needsMarker)
47524
+ reply2 = await this.ensureCompletionMarker(reply2, this.currentSessionId);
47414
47525
  resolveResponse(reply2);
47415
47526
  return reply2;
47416
47527
  }
@@ -47424,7 +47535,9 @@ ${item.payload.content}` + batchSuffix;
47424
47535
  const opts = buildHandleOpts(defaults);
47425
47536
  if (item.sourceType === "a2a_message")
47426
47537
  opts.scenario = "a2a";
47427
- const reply = await this.handleMessage(item.payload.content + markerSuffix, item.metadata?.senderId, senderInfo, opts);
47538
+ let reply = await this.handleMessage(item.payload.content + markerSuffix, item.metadata?.senderId, senderInfo, opts);
47539
+ if (needsMarker)
47540
+ reply = await this.ensureCompletionMarker(reply, opts.sessionId ?? this.currentSessionId);
47428
47541
  resolveResponse(reply);
47429
47542
  return reply;
47430
47543
  }
@@ -47454,7 +47567,10 @@ ${item.payload.content}` + batchSuffix;
47454
47567
  return;
47455
47568
  }
47456
47569
  case "mention": {
47457
- const reply = await this.handleMessage(item.payload.content + markerSuffix, item.metadata?.senderId, senderInfo, buildHandleOpts({ sessionId: `sys_${this.id}_${ts}`, scenario: "a2a" }));
47570
+ const mentionSessionId = `sys_${this.id}_${ts}`;
47571
+ let reply = await this.handleMessage(item.payload.content + markerSuffix, item.metadata?.senderId, senderInfo, buildHandleOpts({ sessionId: mentionSessionId, scenario: "a2a" }));
47572
+ if (needsMarker)
47573
+ reply = await this.ensureCompletionMarker(reply, mentionSessionId);
47458
47574
  resolveResponse(reply);
47459
47575
  return reply;
47460
47576
  }
@@ -47492,7 +47608,10 @@ ${item.payload.content}` + batchSuffix;
47492
47608
  return;
47493
47609
  }
47494
47610
  case "review_request": {
47495
- const reply = await this.handleMessage(item.payload.content + markerSuffix, item.metadata?.senderId, item.metadata?.senderName ? { name: item.metadata.senderName, role: item.metadata.senderRole ?? "worker" } : void 0, buildHandleOpts({ sessionId: `review_${this.id}_${ts}`, scenario: "review" }));
47611
+ const reviewSessionId = `review_${this.id}_${ts}`;
47612
+ let reply = await this.handleMessage(item.payload.content + markerSuffix, item.metadata?.senderId, item.metadata?.senderName ? { name: item.metadata.senderName, role: item.metadata.senderRole ?? "worker" } : void 0, buildHandleOpts({ sessionId: reviewSessionId, scenario: "review" }));
47613
+ if (needsMarker)
47614
+ reply = await this.ensureCompletionMarker(reply, reviewSessionId);
47496
47615
  resolveResponse(reply);
47497
47616
  return reply;
47498
47617
  }
@@ -47507,12 +47626,18 @@ ${item.payload.content}` + batchSuffix;
47507
47626
  }
47508
47627
  case "system_event":
47509
47628
  case "daily_report": {
47510
- const reply = await this.handleMessage(item.payload.content + markerSuffix, void 0, void 0, buildHandleOpts({ sessionId: `sys_${this.id}_${ts}`, scenario: "heartbeat" }));
47629
+ const sysSessionId = `sys_${this.id}_${ts}`;
47630
+ let reply = await this.handleMessage(item.payload.content + markerSuffix, void 0, void 0, buildHandleOpts({ sessionId: sysSessionId, scenario: "heartbeat" }));
47631
+ if (needsMarker)
47632
+ reply = await this.ensureCompletionMarker(reply, sysSessionId);
47511
47633
  resolveResponse(reply);
47512
47634
  return reply;
47513
47635
  }
47514
47636
  case "memory_consolidation": {
47515
- const reply = await this.handleMessage(item.payload.content + markerSuffix, void 0, void 0, buildHandleOpts({ sessionId: `sys_${this.id}_${ts}`, scenario: "memory_consolidation" }));
47637
+ const memSessionId = `sys_${this.id}_${ts}`;
47638
+ let reply = await this.handleMessage(item.payload.content + markerSuffix, void 0, void 0, buildHandleOpts({ sessionId: memSessionId, scenario: "memory_consolidation" }));
47639
+ if (needsMarker)
47640
+ reply = await this.ensureCompletionMarker(reply, memSessionId);
47516
47641
  resolveResponse(reply);
47517
47642
  return reply;
47518
47643
  }
@@ -47521,11 +47646,16 @@ ${item.payload.content}` + batchSuffix;
47521
47646
  const onLog = extra.onLog ?? (() => {
47522
47647
  });
47523
47648
  if (sessionId) {
47524
- const reply2 = await this.respondInSession(sessionId, item.payload.content + markerSuffix, onLog);
47649
+ let reply2 = await this.respondInSession(sessionId, item.payload.content + markerSuffix, onLog);
47650
+ if (needsMarker)
47651
+ reply2 = await this.ensureCompletionMarker(reply2, sessionId);
47525
47652
  resolveResponse(reply2);
47526
47653
  return reply2;
47527
47654
  }
47528
- const reply = await this.handleMessage(item.payload.content + markerSuffix, item.metadata?.senderId, senderInfo, buildHandleOpts({ sessionId: `sys_${this.id}_${ts}` }));
47655
+ const srFallbackSessionId = `sys_${this.id}_${ts}`;
47656
+ let reply = await this.handleMessage(item.payload.content + markerSuffix, item.metadata?.senderId, senderInfo, buildHandleOpts({ sessionId: srFallbackSessionId }));
47657
+ if (needsMarker)
47658
+ reply = await this.ensureCompletionMarker(reply, srFallbackSessionId);
47529
47659
  resolveResponse(reply);
47530
47660
  return reply;
47531
47661
  }
@@ -48493,6 +48623,9 @@ ${instructions}
48493
48623
  setEscalationCallback(cb) {
48494
48624
  this.escalationCallback = cb;
48495
48625
  }
48626
+ setToolCallLimitChecker(cb) {
48627
+ this.toolCallLimitChecker = cb;
48628
+ }
48496
48629
  setStateChangeCallback(cb) {
48497
48630
  this.stateChangeCallback = cb;
48498
48631
  }
@@ -48640,16 +48773,16 @@ ${instructions}
48640
48773
  const toolNames = /* @__PURE__ */ new Set();
48641
48774
  const errors = [];
48642
48775
  let lastText = "";
48643
- for (const log89 of logs) {
48644
- if (log89.type === "tool_start") {
48645
- const name = log89.metadata?.toolName ?? "";
48776
+ for (const log91 of logs) {
48777
+ if (log91.type === "tool_start") {
48778
+ const name = log91.metadata?.toolName ?? "";
48646
48779
  if (name)
48647
48780
  toolNames.add(name);
48648
48781
  }
48649
- if (log89.type === "error")
48650
- errors.push(log89.content.slice(0, 100));
48651
- if (log89.type === "text")
48652
- lastText = log89.content.slice(0, 200);
48782
+ if (log91.type === "error")
48783
+ errors.push(log91.content.slice(0, 100));
48784
+ if (log91.type === "text")
48785
+ lastText = log91.content.slice(0, 200);
48653
48786
  }
48654
48787
  const parts = [];
48655
48788
  if (toolNames.size > 0)
@@ -51040,6 +51173,15 @@ ${body}${contextSuffix}`;
51040
51173
  error: beforeResult.reason ?? "Blocked by tool hook"
51041
51174
  });
51042
51175
  }
51176
+ if (this.toolCallLimitChecker) {
51177
+ const limitResult = this.toolCallLimitChecker();
51178
+ if (!limitResult.allowed) {
51179
+ return JSON.stringify({
51180
+ status: "denied",
51181
+ error: limitResult.reason ?? "Tool call limit reached"
51182
+ });
51183
+ }
51184
+ }
51043
51185
  const baseArgs = beforeResult.modifiedArgs ?? toolCall.arguments;
51044
51186
  const effectiveArgs = sessionId ? { ...baseArgs, _browserSessionId: sessionId } : baseArgs;
51045
51187
  let lastError;
@@ -57831,6 +57973,7 @@ var init_agent_manager = __esm({
57831
57973
  agentAuditCallback;
57832
57974
  escalationHandler;
57833
57975
  approvalHandler;
57976
+ toolCallLimitChecker;
57834
57977
  stateChangeHandler;
57835
57978
  /** Grace timers for releasing scoped MCP processes after agent goes idle */
57836
57979
  mcpReleaseTimers = /* @__PURE__ */ new Map();
@@ -58812,6 +58955,9 @@ Known issues: ${knownIssues}` : ""}`
58812
58955
  const ah = this.approvalHandler;
58813
58956
  agent.setApprovalCallback(async (req) => ah(id, req));
58814
58957
  }
58958
+ if (this.toolCallLimitChecker) {
58959
+ agent.setToolCallLimitChecker(this.toolCallLimitChecker);
58960
+ }
58815
58961
  agent.setStateChangeCallback(this.buildStateChangeCallback());
58816
58962
  if (this.activityCallbacks) {
58817
58963
  agent.setActivityCallbacks(this.activityCallbacks);
@@ -59446,6 +59592,9 @@ Known issues: ${knownIssues}` : ""}`
59446
59592
  const ah = this.approvalHandler;
59447
59593
  agent.setApprovalCallback(async (req) => ah(id, req));
59448
59594
  }
59595
+ if (this.toolCallLimitChecker) {
59596
+ agent.setToolCallLimitChecker(this.toolCallLimitChecker);
59597
+ }
59449
59598
  agent.setStateChangeCallback(this.buildStateChangeCallback());
59450
59599
  if (this.activityCallbacks) {
59451
59600
  agent.setActivityCallbacks(this.activityCallbacks);
@@ -59592,6 +59741,12 @@ Known issues: ${knownIssues}` : ""}`
59592
59741
  agent.setApprovalCallback(async (req) => handler4(id, req));
59593
59742
  }
59594
59743
  }
59744
+ setToolCallLimitChecker(checker) {
59745
+ this.toolCallLimitChecker = checker;
59746
+ for (const [, agent] of this.agents) {
59747
+ agent.setToolCallLimitChecker(checker);
59748
+ }
59749
+ }
59595
59750
  /**
59596
59751
  * Build the combined state-change callback for an agent. Handles:
59597
59752
  * 1. MCP scoped-process lifecycle (release on idle, cancel release on working)
@@ -63859,7 +64014,7 @@ var init_external_gateway = __esm({
63859
64014
  return rows.length;
63860
64015
  }
63861
64016
  async register(request) {
63862
- const { externalAgentId, agentName, orgId: orgId2, capabilities = [], platform: platform4, platformConfig, agentCardUrl, openClawConfig } = request;
64017
+ const { externalAgentId, agentName, orgId: orgId2, capabilities = [], platform: platform5, platformConfig, agentCardUrl, openClawConfig } = request;
63863
64018
  if (!externalAgentId || !agentName || !orgId2) {
63864
64019
  throw new GatewayError("Missing required fields: externalAgentId, agentName, orgId", 400);
63865
64020
  }
@@ -63886,7 +64041,7 @@ var init_external_gateway = __esm({
63886
64041
  agentName,
63887
64042
  orgId: orgId2,
63888
64043
  capabilities,
63889
- platform: platform4 ?? (openClawConfig ? "openclaw" : void 0),
64044
+ platform: platform5 ?? (openClawConfig ? "openclaw" : void 0),
63890
64045
  platformConfig: platformConfig ?? openClawConfig,
63891
64046
  agentCardUrl,
63892
64047
  openClawConfig,
@@ -76683,8 +76838,8 @@ var require_CronFileParser = __commonJS({
76683
76838
  * @throws If file cannot be read
76684
76839
  */
76685
76840
  static parseFileSync(filePath) {
76686
- const { readFileSync: readFileSync29 } = __require("fs");
76687
- const data = readFileSync29(filePath, "utf8");
76841
+ const { readFileSync: readFileSync31 } = __require("fs");
76842
+ const data = readFileSync31(filePath, "utf8");
76688
76843
  return _CronFileParser.#parseContent(data);
76689
76844
  }
76690
76845
  /**
@@ -82013,6 +82168,8 @@ var init_api_server = __esm({
82013
82168
  hitlService;
82014
82169
  billingService;
82015
82170
  auditService;
82171
+ licenseService;
82172
+ telemetryService;
82016
82173
  storage;
82017
82174
  llmRouter;
82018
82175
  markusConfigPath;
@@ -82032,7 +82189,40 @@ var init_api_server = __esm({
82032
82189
  remoteAgent;
82033
82190
  remoteAgentFactory;
82034
82191
  modelCatalog;
82035
- // Custom group chats are now persisted in SQLite via storage.groupChatRepo
82192
+ /** Aggregate today's tool calls from all agents' persisted metrics (the single source of truth) */
82193
+ getToolCallsTodayFromAgents() {
82194
+ try {
82195
+ const agentManager = this.orgService.getAgentManager();
82196
+ const allAgents = agentManager.listAgents();
82197
+ let total = 0;
82198
+ for (const a of allAgents) {
82199
+ try {
82200
+ const agent = agentManager.getAgent(a.id);
82201
+ total += agent.getUsageStats().toolCallsToday;
82202
+ } catch {
82203
+ }
82204
+ }
82205
+ return total;
82206
+ } catch {
82207
+ return 0;
82208
+ }
82209
+ }
82210
+ /** fetch that follows redirects while preserving the Authorization header */
82211
+ async hubFetch(url, init) {
82212
+ let currentUrl = url;
82213
+ for (let i = 0; i < 3; i++) {
82214
+ const res = await fetch(currentUrl, { ...init, redirect: "manual" });
82215
+ if (res.status >= 300 && res.status < 400) {
82216
+ const location = res.headers.get("location");
82217
+ if (!location)
82218
+ return res;
82219
+ currentUrl = new URL(location, currentUrl).href;
82220
+ continue;
82221
+ }
82222
+ return res;
82223
+ }
82224
+ return fetch(currentUrl, init);
82225
+ }
82036
82226
  constructor(orgService, taskService, port = 8056) {
82037
82227
  this.orgService = orgService;
82038
82228
  this.taskService = taskService;
@@ -82241,7 +82431,7 @@ ${cleanText}`,
82241
82431
  const headers = { "Content-Type": "application/json" };
82242
82432
  if (token)
82243
82433
  headers["Authorization"] = `Bearer ${token}`;
82244
- const res = await fetch(`${hubUrl}/api/items${qs ? `?${qs}` : ""}`, { headers });
82434
+ const res = await self.hubFetch(`${hubUrl}/api/items${qs ? `?${qs}` : ""}`, { headers });
82245
82435
  if (!res.ok)
82246
82436
  throw new Error(`Hub search failed: ${res.status}`);
82247
82437
  const data = await res.json();
@@ -82261,7 +82451,7 @@ ${cleanText}`,
82261
82451
  if (!token)
82262
82452
  throw new Error("Hub token not configured. Please login to Markus Hub first.");
82263
82453
  const headers = { "Content-Type": "application/json", "Authorization": `Bearer ${token}` };
82264
- const res = await fetch(`${hubUrl}/api/items/${itemId}/download`, { method: "POST", headers });
82454
+ const res = await self.hubFetch(`${hubUrl}/api/items/${itemId}/download`, { method: "POST", headers });
82265
82455
  if (!res.ok)
82266
82456
  throw new Error(`Hub download failed: ${res.status}`);
82267
82457
  const data = await res.json();
@@ -82295,6 +82485,12 @@ ${cleanText}`,
82295
82485
  setBillingService(service) {
82296
82486
  this.billingService = service;
82297
82487
  }
82488
+ setLicenseService(service) {
82489
+ this.licenseService = service;
82490
+ }
82491
+ setTelemetryService(service) {
82492
+ this.telemetryService = service;
82493
+ }
82298
82494
  setAuditService(service) {
82299
82495
  this.auditService = service;
82300
82496
  }
@@ -83047,12 +83243,18 @@ ${cleanText}`,
83047
83243
  }
83048
83244
  if (path === "/api/auth/status" && req.method === "GET") {
83049
83245
  if (!this.storage || !this.authEnabled) {
83050
- this.json(res, 200, { initialized: true });
83246
+ this.json(res, 200, { initialized: true, hasOwner: true, hasMultipleUsers: false });
83051
83247
  return;
83052
83248
  }
83053
83249
  const allUsers = await this.storage.userRepo.listByOrg("default");
83054
- const hasRealUsers = allUsers.some((u) => u.passwordHash && u.email !== "admin@markus.local");
83055
- this.json(res, 200, { initialized: hasRealUsers });
83250
+ const realUsers = allUsers.filter((u) => (u.passwordHash || u.hubUserId) && u.email !== "admin@markus.local");
83251
+ const hasOwner = realUsers.some((u) => u.role === "owner");
83252
+ const hasMultipleUsers = realUsers.length > 1;
83253
+ this.json(res, 200, {
83254
+ initialized: realUsers.length > 0,
83255
+ hasOwner,
83256
+ hasMultipleUsers
83257
+ });
83056
83258
  return;
83057
83259
  }
83058
83260
  if (path === "/api/auth/init" && req.method === "POST") {
@@ -83154,6 +83356,135 @@ ${cleanText}`,
83154
83356
  });
83155
83357
  return;
83156
83358
  }
83359
+ if (path === "/api/auth/hub-login" && req.method === "POST") {
83360
+ if (!this.storage) {
83361
+ this.json(res, 503, { error: "Storage not available" });
83362
+ return;
83363
+ }
83364
+ const body = await this.readBody(req);
83365
+ const hubToken = body["hubToken"];
83366
+ const hubUser = body["hubUser"];
83367
+ if (!hubToken || !hubUser?.id) {
83368
+ this.json(res, 400, { error: "hubToken and hubUser are required" });
83369
+ return;
83370
+ }
83371
+ let verifiedUser = null;
83372
+ try {
83373
+ const verifyRes = await this.hubFetch(`${this.hubUrl}/api/auth/me`, {
83374
+ headers: { "Authorization": `Bearer ${hubToken}` }
83375
+ });
83376
+ if (verifyRes.ok) {
83377
+ const verifyData = await verifyRes.json();
83378
+ if (verifyData.user && verifyData.user.id === hubUser.id) {
83379
+ verifiedUser = verifyData.user;
83380
+ } else {
83381
+ log61.warn("Hub token user mismatch", { expected: hubUser.id, got: verifyData.user?.id });
83382
+ }
83383
+ } else {
83384
+ log61.warn("Hub /api/auth/me returned non-OK", { status: verifyRes.status, hubUrl: this.hubUrl });
83385
+ }
83386
+ } catch (e) {
83387
+ log61.warn("Hub token verification failed, proceeding with client-supplied data", { error: e.message, hubUrl: this.hubUrl });
83388
+ }
83389
+ if (!verifiedUser) {
83390
+ verifiedUser = { id: hubUser.id, username: hubUser.username, email: hubUser.email, displayName: hubUser.displayName, avatarUrl: hubUser.avatarUrl };
83391
+ }
83392
+ const rawEmail = (verifiedUser.email ?? hubUser.email ?? "").trim().toLowerCase();
83393
+ const email = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(rawEmail) ? rawEmail : "";
83394
+ const name = verifiedUser.displayName || verifiedUser.username || hubUser.displayName || hubUser.username || email && email.split("@")[0] || "User";
83395
+ const avatarUrl = verifiedUser.avatarUrl ?? hubUser.avatarUrl ?? null;
83396
+ let userRow = this.storage.userRepo.findByHubUserId(hubUser.id);
83397
+ let isFirstLogin = false;
83398
+ if (!userRow && email) {
83399
+ userRow = this.storage.userRepo.findByEmail(email);
83400
+ if (userRow) {
83401
+ this.storage.userRepo.updateHubUserId(userRow.id, hubUser.id, hubUser.username);
83402
+ if (avatarUrl && !userRow.avatarUrl) {
83403
+ this.storage.userRepo.updateAvatarUrl(userRow.id, avatarUrl);
83404
+ }
83405
+ }
83406
+ }
83407
+ if (!userRow) {
83408
+ const allUsers = await this.storage.userRepo.listByOrg("default");
83409
+ const placeholder = allUsers.find((u) => u.role === "owner" && u.email === "admin@markus.local");
83410
+ const hasRealOwner = allUsers.some((u) => u.role === "owner" && (u.passwordHash || u.hubUserId) && u.email !== "admin@markus.local");
83411
+ if (placeholder && !hasRealOwner) {
83412
+ this.storage.userRepo.updateProfile(placeholder.id, { name, email: email || void 0, avatarUrl });
83413
+ this.storage.userRepo.updateHubUserId(placeholder.id, hubUser.id, hubUser.username);
83414
+ userRow = this.storage.userRepo.findById(placeholder.id);
83415
+ isFirstLogin = true;
83416
+ } else if (!hasRealOwner) {
83417
+ const userId2 = userId();
83418
+ this.storage.userRepo.create({
83419
+ id: userId2,
83420
+ orgId: "default",
83421
+ name,
83422
+ email: email || void 0,
83423
+ role: "owner",
83424
+ hubUserId: hubUser.id,
83425
+ avatarUrl: avatarUrl ?? void 0
83426
+ });
83427
+ this.storage.userRepo.updateHubUserId(userId2, hubUser.id, hubUser.username);
83428
+ userRow = this.storage.userRepo.findById(userId2);
83429
+ isFirstLogin = true;
83430
+ } else {
83431
+ const realOwners = allUsers.filter((u) => u.role === "owner" && (u.passwordHash || u.hubUserId) && u.email !== "admin@markus.local");
83432
+ if (realOwners.length === 1 && !realOwners[0].hubUserId) {
83433
+ const existingOwner = realOwners[0];
83434
+ this.storage.userRepo.updateProfile(existingOwner.id, { name: existingOwner.name, email: email || existingOwner.email, avatarUrl: avatarUrl ?? existingOwner.avatarUrl });
83435
+ this.storage.userRepo.updateHubUserId(existingOwner.id, hubUser.id, hubUser.username);
83436
+ userRow = this.storage.userRepo.findById(existingOwner.id);
83437
+ log61.info("Hub login: adopted existing owner", { ownerId: existingOwner.id, hubUserId: hubUser.id });
83438
+ } else {
83439
+ this.json(res, 403, { error: "This instance already has an owner. Multi-user requires Enterprise license." });
83440
+ return;
83441
+ }
83442
+ }
83443
+ }
83444
+ if (!userRow) {
83445
+ this.json(res, 500, { error: "Failed to create user" });
83446
+ return;
83447
+ }
83448
+ const hubUsername = verifiedUser.username || hubUser.username;
83449
+ if (hubUsername) {
83450
+ this.storage.userRepo.updateHubUserId(userRow.id, hubUser.id, hubUsername);
83451
+ }
83452
+ const profileUpdates = {};
83453
+ if (name && name !== userRow.name)
83454
+ profileUpdates.name = name;
83455
+ if (email && email !== userRow.email)
83456
+ profileUpdates.email = email;
83457
+ if (avatarUrl && avatarUrl !== userRow.avatarUrl)
83458
+ profileUpdates.avatarUrl = avatarUrl;
83459
+ if (Object.keys(profileUpdates).length > 0) {
83460
+ this.storage.userRepo.updateProfile(userRow.id, profileUpdates);
83461
+ userRow = this.storage.userRepo.findById(userRow.id);
83462
+ }
83463
+ this.orgService.syncHumanIdentity(userRow.id, "default", userRow.name, userRow.role, userRow.email ?? void 0);
83464
+ try {
83465
+ const tokenPath = join24(homedir15(), ".markus", "hub-token");
83466
+ mkdirSync19(dirname8(tokenPath), { recursive: true });
83467
+ writeFileSync17(tokenPath, hubToken, "utf-8");
83468
+ } catch {
83469
+ }
83470
+ const finalUser = userRow;
83471
+ await this.storage.userRepo.updateLastLogin(finalUser.id);
83472
+ const exp = Math.floor(Date.now() / 1e3) + 7 * 24 * 3600;
83473
+ const token = await signToken({ userId: finalUser.id, orgId: "default", role: finalUser.role, exp }, this.jwtSecret);
83474
+ res.setHeader("Set-Cookie", `markus_token=${token}; HttpOnly; SameSite=Strict; Path=/; Max-Age=${7 * 24 * 3600}`);
83475
+ this.json(res, 200, {
83476
+ user: {
83477
+ id: finalUser.id,
83478
+ name: finalUser.name,
83479
+ email: finalUser.email,
83480
+ role: finalUser.role,
83481
+ orgId: finalUser.orgId,
83482
+ avatarUrl: finalUser.avatarUrl ?? void 0
83483
+ },
83484
+ needsOnboarding: isFirstLogin
83485
+ });
83486
+ return;
83487
+ }
83157
83488
  if (path === "/api/auth/logout" && req.method === "POST") {
83158
83489
  res.setHeader("Set-Cookie", "markus_token=; HttpOnly; SameSite=Strict; Path=/; Max-Age=0");
83159
83490
  this.json(res, 200, { ok: true });
@@ -84242,6 +84573,16 @@ ${cleanText}`,
84242
84573
  this.json(res, 400, { error: "name is required" });
84243
84574
  return;
84244
84575
  }
84576
+ if (this.licenseService) {
84577
+ const limits = this.licenseService.getLimits();
84578
+ if (limits.maxTeams > 0) {
84579
+ const existingTeams = await this.orgService.listTeams(orgId2);
84580
+ if (existingTeams.length >= limits.maxTeams) {
84581
+ this.json(res, 403, { error: `Team limit reached (${limits.maxTeams}). Upgrade to Enterprise for unlimited teams.` });
84582
+ return;
84583
+ }
84584
+ }
84585
+ }
84245
84586
  const team = await this.orgService.createTeam(orgId2, name, body["description"]);
84246
84587
  this.ws?.broadcast({
84247
84588
  type: "chat:group_created",
@@ -86372,6 +86713,16 @@ EXPLANATION_END`;
86372
86713
  const authUser = await this.requireAuth(req, res);
86373
86714
  if (!authUser)
86374
86715
  return;
86716
+ if (this.licenseService && this.storage) {
86717
+ const limits = this.licenseService.getLimits();
86718
+ if (limits.maxUsers > 0) {
86719
+ const existingCount = this.storage.userRepo.countByOrg("default");
86720
+ if (existingCount >= limits.maxUsers) {
86721
+ this.json(res, 403, { error: `User limit reached (${limits.maxUsers}). Upgrade to Enterprise for multi-user support.` });
86722
+ return;
86723
+ }
86724
+ }
86725
+ }
86375
86726
  const body = await this.readBody(req);
86376
86727
  const orgId2 = body["orgId"] ?? "default";
86377
86728
  const name = body["name"];
@@ -86652,14 +87003,14 @@ EXPLANATION_END`;
86652
87003
  const installedSkills = new Map((this.skillRegistry?.list() ?? []).map((s2) => [s2.name, s2]));
86653
87004
  const rawManifests = /* @__PURE__ */ new Map();
86654
87005
  try {
86655
- const { readdirSync: readdirSync14, readFileSync: readFileSync29, existsSync: existsSync40 } = await import("node:fs");
87006
+ const { readdirSync: readdirSync14, readFileSync: readFileSync31, existsSync: existsSync42 } = await import("node:fs");
86656
87007
  for (const entry of readdirSync14(builtinDir, { withFileTypes: true })) {
86657
87008
  if (!entry.isDirectory())
86658
87009
  continue;
86659
87010
  const sjPath = resolve15(builtinDir, entry.name, "skill.json");
86660
- if (existsSync40(sjPath)) {
87011
+ if (existsSync42(sjPath)) {
86661
87012
  try {
86662
- rawManifests.set(entry.name, JSON.parse(readFileSync29(sjPath, "utf-8")));
87013
+ rawManifests.set(entry.name, JSON.parse(readFileSync31(sjPath, "utf-8")));
86663
87014
  } catch {
86664
87015
  }
86665
87016
  }
@@ -87086,6 +87437,16 @@ EXPLANATION_END`;
87086
87437
  this.json(res, 500, { error: "BuilderService not initialized" });
87087
87438
  return;
87088
87439
  }
87440
+ if (type === "team" && this.licenseService) {
87441
+ const limits = this.licenseService.getLimits();
87442
+ if (limits.maxTeams > 0) {
87443
+ const existingTeams = await this.orgService.listTeams("default");
87444
+ if (existingTeams.length >= limits.maxTeams) {
87445
+ this.json(res, 403, { error: `Team limit reached (${limits.maxTeams}). Upgrade to Enterprise for unlimited teams.` });
87446
+ return;
87447
+ }
87448
+ }
87449
+ }
87089
87450
  try {
87090
87451
  const result = await this.builderService.installArtifact(type, name);
87091
87452
  this.json(res, 201, result);
@@ -88101,11 +88462,11 @@ EXPLANATION_END`;
88101
88462
  }
88102
88463
  if (path === "/api/templates/teams" && req.method === "GET") {
88103
88464
  try {
88104
- const { readdirSync: readdirSync14, readFileSync: readFileSync29 } = await import("node:fs");
88465
+ const { readdirSync: readdirSync14, readFileSync: readFileSync31 } = await import("node:fs");
88105
88466
  const { resolve: resolve21 } = await import("node:path");
88106
88467
  const teamsDir = resolve21(process.cwd(), "templates", "teams");
88107
88468
  const files = readdirSync14(teamsDir).filter((f) => f.endsWith(".json"));
88108
- const teams = files.map((f) => JSON.parse(readFileSync29(resolve21(teamsDir, f), "utf-8")));
88469
+ const teams = files.map((f) => JSON.parse(readFileSync31(resolve21(teamsDir, f), "utf-8")));
88109
88470
  this.json(res, 200, { templates: teams });
88110
88471
  } catch {
88111
88472
  this.json(res, 200, { templates: [] });
@@ -88146,6 +88507,183 @@ EXPLANATION_END`;
88146
88507
  this.json(res, 200, { usage });
88147
88508
  return;
88148
88509
  }
88510
+ if (path === "/api/license" && req.method === "GET") {
88511
+ const raw = this.licenseService ? this.licenseService.getInfo() : { plan: "free", features: [], limits: { maxTeams: 1, maxToolCallsPerDay: 500, maxUsers: 1 } };
88512
+ const info2 = { ...raw };
88513
+ const authUser = await this.getAuthUser(req);
88514
+ if (authUser && this.storage) {
88515
+ const userRow = this.storage.userRepo.findById(authUser.userId);
88516
+ if (userRow) {
88517
+ if (userRow.hubUserId)
88518
+ info2.hubUserId = userRow.hubUserId;
88519
+ info2.username = userRow.hubUsername || userRow.name || void 0;
88520
+ }
88521
+ }
88522
+ try {
88523
+ const defaultOrg = this.orgService.getDefaultOrganization();
88524
+ const orgId2 = defaultOrg?.id ?? "default";
88525
+ const teams = this.orgService.listTeams(orgId2);
88526
+ const humans = this.orgService.listHumanUsers(orgId2);
88527
+ const todayToolCalls = this.getToolCallsTodayFromAgents();
88528
+ info2.usage = { teams: teams.length, toolCallsToday: todayToolCalls, users: humans.length };
88529
+ } catch {
88530
+ }
88531
+ const hubToken = this.readHubToken();
88532
+ if (hubToken) {
88533
+ try {
88534
+ const meRes = await this.hubFetch(`${this.hubUrl}/api/auth/me`, {
88535
+ headers: { "Authorization": `Bearer ${hubToken}` }
88536
+ });
88537
+ if (meRes.ok) {
88538
+ const meData = await meRes.json();
88539
+ if (meData.defaultOrg) {
88540
+ info2.defaultOrg = meData.defaultOrg;
88541
+ if (!info2.orgId)
88542
+ info2.orgId = meData.defaultOrg.id;
88543
+ if (!info2.orgName)
88544
+ info2.orgName = meData.defaultOrg.name;
88545
+ }
88546
+ if (meData.user?.id && !info2.hubUserId)
88547
+ info2.hubUserId = meData.user.id;
88548
+ }
88549
+ } catch {
88550
+ }
88551
+ }
88552
+ this.json(res, 200, info2);
88553
+ return;
88554
+ }
88555
+ if (path === "/api/license/refresh" && req.method === "POST") {
88556
+ if (!this.licenseService) {
88557
+ this.json(res, 503, { error: "License service not available" });
88558
+ return;
88559
+ }
88560
+ const raw = await this.licenseService.revalidate();
88561
+ if (this.billingService)
88562
+ this.billingService.setOrgPlan("default", this.licenseService.getPlan());
88563
+ const info2 = { ...raw };
88564
+ const authUser = await this.getAuthUser(req);
88565
+ if (authUser && this.storage) {
88566
+ const userRow = this.storage.userRepo.findById(authUser.userId);
88567
+ if (userRow) {
88568
+ if (userRow.hubUserId)
88569
+ info2.hubUserId = userRow.hubUserId;
88570
+ info2.username = userRow.hubUsername || userRow.name || void 0;
88571
+ }
88572
+ }
88573
+ try {
88574
+ const defaultOrg = this.orgService.getDefaultOrganization();
88575
+ const orgId2 = defaultOrg?.id ?? "default";
88576
+ const teams = this.orgService.listTeams(orgId2);
88577
+ const humans = this.orgService.listHumanUsers(orgId2);
88578
+ const todayToolCalls = this.getToolCallsTodayFromAgents();
88579
+ info2.usage = { teams: teams.length, toolCallsToday: todayToolCalls, users: humans.length };
88580
+ } catch {
88581
+ }
88582
+ const hubToken = this.readHubToken();
88583
+ if (hubToken) {
88584
+ try {
88585
+ const meRes = await this.hubFetch(`${this.hubUrl}/api/auth/me`, {
88586
+ headers: { "Authorization": `Bearer ${hubToken}` }
88587
+ });
88588
+ if (meRes.ok) {
88589
+ const meData = await meRes.json();
88590
+ if (meData.defaultOrg) {
88591
+ info2.defaultOrg = meData.defaultOrg;
88592
+ if (!info2.orgId)
88593
+ info2.orgId = meData.defaultOrg.id;
88594
+ if (!info2.orgName)
88595
+ info2.orgName = meData.defaultOrg.name;
88596
+ }
88597
+ if (meData.user?.id && !info2.hubUserId)
88598
+ info2.hubUserId = meData.user.id;
88599
+ }
88600
+ } catch {
88601
+ }
88602
+ }
88603
+ this.json(res, 200, info2);
88604
+ return;
88605
+ }
88606
+ if (path === "/api/license/activate" && req.method === "POST") {
88607
+ const authUser = await this.requireAuth(req, res);
88608
+ if (!authUser)
88609
+ return;
88610
+ if (!this.licenseService) {
88611
+ this.json(res, 503, { error: "License service not available" });
88612
+ return;
88613
+ }
88614
+ const body = await this.readBody(req);
88615
+ const licenseKey = body["licenseKey"];
88616
+ if (!licenseKey) {
88617
+ this.json(res, 400, { error: "licenseKey is required" });
88618
+ return;
88619
+ }
88620
+ const result = await this.licenseService.activateLicense(licenseKey);
88621
+ if (result.success && this.billingService)
88622
+ this.billingService.setOrgPlan("default", this.licenseService.getPlan());
88623
+ this.json(res, result.success ? 200 : 400, result);
88624
+ return;
88625
+ }
88626
+ if (path === "/api/license/trial" && req.method === "POST") {
88627
+ const authUser = await this.requireAuth(req, res);
88628
+ if (!authUser)
88629
+ return;
88630
+ if (!this.licenseService) {
88631
+ this.json(res, 503, { error: "License service not available" });
88632
+ return;
88633
+ }
88634
+ const result = await this.licenseService.activateTrial();
88635
+ if (result.success && this.billingService)
88636
+ this.billingService.setOrgPlan("default", this.licenseService.getPlan());
88637
+ this.json(res, result.success ? 200 : 400, result);
88638
+ return;
88639
+ }
88640
+ if (path === "/api/license/import" && req.method === "POST") {
88641
+ const authUser = await this.requireAuth(req, res);
88642
+ if (!authUser)
88643
+ return;
88644
+ if (!this.licenseService) {
88645
+ this.json(res, 503, { error: "License service not available" });
88646
+ return;
88647
+ }
88648
+ const body = await this.readBody(req);
88649
+ const fileContent = body["fileContent"];
88650
+ if (!fileContent) {
88651
+ this.json(res, 400, { error: "fileContent is required" });
88652
+ return;
88653
+ }
88654
+ const result = this.licenseService.importOfflineLicense(fileContent);
88655
+ if (result.success && this.billingService)
88656
+ this.billingService.setOrgPlan("default", this.licenseService.getPlan());
88657
+ this.json(res, result.success ? 200 : 400, result);
88658
+ return;
88659
+ }
88660
+ if (path === "/api/license/deactivate" && req.method === "POST") {
88661
+ const authUser = await this.requireAuth(req, res);
88662
+ if (!authUser)
88663
+ return;
88664
+ if (!this.licenseService) {
88665
+ this.json(res, 503, { error: "License service not available" });
88666
+ return;
88667
+ }
88668
+ await this.licenseService.deactivate();
88669
+ if (this.billingService)
88670
+ this.billingService.setOrgPlan("default", this.licenseService.getPlan());
88671
+ this.json(res, 200, { ok: true });
88672
+ return;
88673
+ }
88674
+ if (path === "/api/settings/telemetry" && req.method === "POST") {
88675
+ const body = await this.readBody(req);
88676
+ const enabled = body["enabled"];
88677
+ if (this.telemetryService && typeof enabled === "boolean") {
88678
+ this.telemetryService.setEnabled(enabled);
88679
+ }
88680
+ this.json(res, 200, { ok: true });
88681
+ return;
88682
+ }
88683
+ if (path === "/api/settings/telemetry" && req.method === "GET") {
88684
+ this.json(res, 200, { enabled: this.telemetryService?.isEnabled() ?? false });
88685
+ return;
88686
+ }
88149
88687
  if (path === "/api/hub/publish" && req.method === "POST") {
88150
88688
  const authUser = await this.requireAuth(req, res);
88151
88689
  if (!authUser)
@@ -88193,8 +88731,16 @@ EXPLANATION_END`;
88193
88731
  else
88194
88732
  proxyHeaders["Content-Type"] = req.headers["content-type"];
88195
88733
  const authHeader = req.headers["authorization"];
88196
- if (authHeader)
88734
+ if (authHeader) {
88197
88735
  proxyHeaders["Authorization"] = authHeader;
88736
+ } else {
88737
+ const storedToken = this.readHubToken();
88738
+ if (storedToken)
88739
+ proxyHeaders["Authorization"] = `Bearer ${storedToken}`;
88740
+ }
88741
+ if (req.headers["accept-language"]) {
88742
+ proxyHeaders["Accept-Language"] = req.headers["accept-language"];
88743
+ }
88198
88744
  try {
88199
88745
  let body;
88200
88746
  if (req.method === "POST" || req.method === "PUT" || req.method === "PATCH") {
@@ -88279,6 +88825,10 @@ EXPLANATION_END`;
88279
88825
  }
88280
88826
  this.remoteAgent.start().catch(() => {
88281
88827
  });
88828
+ try {
88829
+ saveConfig({ remote: { enabled: true } }, this.markusConfigPath);
88830
+ } catch {
88831
+ }
88282
88832
  this.json(res, 200, { ok: true, status: this.remoteAgent.getStatus() });
88283
88833
  return;
88284
88834
  }
@@ -88286,6 +88836,10 @@ EXPLANATION_END`;
88286
88836
  if (this.remoteAgent) {
88287
88837
  await this.remoteAgent.stop();
88288
88838
  }
88839
+ try {
88840
+ saveConfig({ remote: { enabled: false } }, this.markusConfigPath);
88841
+ } catch {
88842
+ }
88289
88843
  this.json(res, 200, { ok: true });
88290
88844
  return;
88291
88845
  }
@@ -88563,7 +89117,7 @@ EXPLANATION_END`;
88563
89117
  const { fileURLToPath: fileURLToPath8 } = await import("node:url");
88564
89118
  const { dirname: dn, resolve: rslv, join: jn } = await import("node:path");
88565
89119
  const { execSync: execSync7 } = await import("node:child_process");
88566
- const { existsSync: ex, readFileSync: readFileSync29, statSync: statSync8 } = await import("node:fs");
89120
+ const { existsSync: ex, readFileSync: readFileSync31, statSync: statSync8 } = await import("node:fs");
88567
89121
  const thisDir = dn(fileURLToPath8(import.meta.url));
88568
89122
  const zipCandidates = [
88569
89123
  jn(rslv(thisDir, "..", "..", "chrome-extension"), "dist", "markus-browser-extension.zip"),
@@ -88592,7 +89146,7 @@ EXPLANATION_END`;
88592
89146
  this.json(res, 404, { error: "Extension zip not found." });
88593
89147
  return;
88594
89148
  }
88595
- const data = readFileSync29(zipPath);
89149
+ const data = readFileSync31(zipPath);
88596
89150
  res.writeHead(200, {
88597
89151
  "Content-Type": "application/zip",
88598
89152
  "Content-Disposition": 'attachment; filename="markus-browser-extension.zip"',
@@ -88610,11 +89164,11 @@ EXPLANATION_END`;
88610
89164
  return;
88611
89165
  try {
88612
89166
  const { exec: execCb2 } = await import("node:child_process");
88613
- const platform4 = process.platform;
88614
- if (platform4 === "darwin") {
89167
+ const platform5 = process.platform;
89168
+ if (platform5 === "darwin") {
88615
89169
  execCb2('open -a "Google Chrome" "chrome://extensions"', () => {
88616
89170
  });
88617
- } else if (platform4 === "win32") {
89171
+ } else if (platform5 === "win32") {
88618
89172
  execCb2('start "" "chrome://extensions"', () => {
88619
89173
  });
88620
89174
  } else {
@@ -89693,11 +90247,11 @@ data: ${JSON.stringify({ error: msg })}
89693
90247
  const { configPath, preview } = body;
89694
90248
  const { existsSync: fsExists, readFileSync: fsRead } = await import("node:fs");
89695
90249
  const { join: pathJoin } = await import("node:path");
89696
- const { homedir: homedir23 } = await import("node:os");
90250
+ const { homedir: homedir25 } = await import("node:os");
89697
90251
  const possiblePaths = [
89698
90252
  configPath,
89699
- pathJoin(homedir23(), ".openclaw", "openclaw.json"),
89700
- pathJoin(homedir23(), ".openclaw", "openclaw.json5")
90253
+ pathJoin(homedir25(), ".openclaw", "openclaw.json"),
90254
+ pathJoin(homedir25(), ".openclaw", "openclaw.json5")
89701
90255
  ].filter(Boolean);
89702
90256
  let found = "";
89703
90257
  let rawContent = "";
@@ -89916,10 +90470,10 @@ data: ${JSON.stringify({ error: msg })}
89916
90470
  this.json(res, 400, { error: "Invalid or non-existent path" });
89917
90471
  return;
89918
90472
  }
89919
- const platform4 = process.platform;
89920
- if (platform4 === "darwin")
90473
+ const platform5 = process.platform;
90474
+ if (platform5 === "darwin")
89921
90475
  execSync3(`open ${JSON.stringify(dirPath)}`);
89922
- else if (platform4 === "win32")
90476
+ else if (platform5 === "win32")
89923
90477
  execSync3(`explorer ${JSON.stringify(dirPath)}`);
89924
90478
  else
89925
90479
  execSync3(`xdg-open ${JSON.stringify(dirPath)}`);
@@ -90071,9 +90625,9 @@ data: ${JSON.stringify({ error: msg })}
90071
90625
  }
90072
90626
  try {
90073
90627
  const { resolve: resolve21, extname: extname2 } = await import("node:path");
90074
- const { existsSync: existsSync40, statSync: statSync8 } = await import("node:fs");
90075
- const { homedir: homedir23 } = await import("node:os");
90076
- const home = homedir23();
90628
+ const { existsSync: existsSync42, statSync: statSync8 } = await import("node:fs");
90629
+ const { homedir: homedir25 } = await import("node:os");
90630
+ const home = homedir25();
90077
90631
  const results = {};
90078
90632
  const mdExts = [".md", ".markdown"];
90079
90633
  const htmlExts = [".html", ".htm"];
@@ -90083,7 +90637,7 @@ data: ${JSON.stringify({ error: msg })}
90083
90637
  try {
90084
90638
  const expanded = p.startsWith("~/") ? resolve21(home, p.slice(2)) : p === "~" ? home : p;
90085
90639
  const resolved = resolve21(expanded);
90086
- if (!existsSync40(resolved)) {
90640
+ if (!existsSync42(resolved)) {
90087
90641
  results[p] = { exists: false, isFile: false, type: "unknown" };
90088
90642
  continue;
90089
90643
  }
@@ -90120,21 +90674,21 @@ data: ${JSON.stringify({ error: msg })}
90120
90674
  }
90121
90675
  try {
90122
90676
  const { resolve: resolve21, extname: extname2 } = await import("node:path");
90123
- const { readFileSync: readFileSync29, existsSync: existsSync40, statSync: statSync8 } = await import("node:fs");
90124
- const { homedir: homedir23 } = await import("node:os");
90125
- const home = homedir23();
90677
+ const { readFileSync: readFileSync31, existsSync: existsSync42, statSync: statSync8 } = await import("node:fs");
90678
+ const { homedir: homedir25 } = await import("node:os");
90679
+ const home = homedir25();
90126
90680
  const expanded = filePath.startsWith("~/") ? resolve21(home, filePath.slice(2)) : filePath === "~" ? home : filePath;
90127
90681
  const resolved = resolve21(expanded);
90128
- if (!existsSync40(resolved)) {
90682
+ if (!existsSync42(resolved)) {
90129
90683
  this.json(res, 404, { error: "File not found" });
90130
90684
  return;
90131
90685
  }
90132
90686
  const stat = statSync8(resolved);
90133
90687
  if (stat.isDirectory()) {
90134
90688
  const { readdirSync: readdirSync14 } = await import("node:fs");
90135
- const { join: join33, extname: extDir } = await import("node:path");
90689
+ const { join: join35, extname: extDir } = await import("node:path");
90136
90690
  const entries2 = readdirSync14(resolved, { withFileTypes: true }).filter((e) => !e.name.startsWith(".")).map((e) => {
90137
- const full = join33(resolved, e.name);
90691
+ const full = join35(resolved, e.name);
90138
90692
  const isDir = e.isDirectory();
90139
90693
  let size;
90140
90694
  try {
@@ -90163,7 +90717,7 @@ data: ${JSON.stringify({ error: msg })}
90163
90717
  const ext = extname2(resolved).toLowerCase();
90164
90718
  const imageExts = [".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg"];
90165
90719
  if (imageExts.includes(ext)) {
90166
- const data = readFileSync29(resolved);
90720
+ const data = readFileSync31(resolved);
90167
90721
  const mimeMap = { ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".gif": "image/gif", ".webp": "image/webp", ".svg": "image/svg+xml" };
90168
90722
  this.json(res, 200, {
90169
90723
  type: "image",
@@ -90172,7 +90726,7 @@ data: ${JSON.stringify({ error: msg })}
90172
90726
  content: data.toString("base64")
90173
90727
  });
90174
90728
  } else {
90175
- const content = readFileSync29(resolved, "utf-8");
90729
+ const content = readFileSync31(resolved, "utf-8");
90176
90730
  const mdExts = [".md", ".markdown"];
90177
90731
  const htmlExts = [".html", ".htm"];
90178
90732
  const jsonExts = [".json"];
@@ -90205,12 +90759,12 @@ data: ${JSON.stringify({ error: msg })}
90205
90759
  }
90206
90760
  try {
90207
90761
  const { resolve: resolve21, extname: extname2 } = await import("node:path");
90208
- const { readFileSync: readFileSync29, existsSync: existsSync40, statSync: statSync8 } = await import("node:fs");
90209
- const { homedir: homedir23 } = await import("node:os");
90210
- const home = homedir23();
90762
+ const { readFileSync: readFileSync31, existsSync: existsSync42, statSync: statSync8 } = await import("node:fs");
90763
+ const { homedir: homedir25 } = await import("node:os");
90764
+ const home = homedir25();
90211
90765
  const expanded = filePath.startsWith("~/") ? resolve21(home, filePath.slice(2)) : filePath === "~" ? home : filePath;
90212
90766
  const resolved = resolve21(expanded);
90213
- if (!existsSync40(resolved) || !statSync8(resolved).isFile()) {
90767
+ if (!existsSync42(resolved) || !statSync8(resolved).isFile()) {
90214
90768
  this.json(res, 404, { error: "Image not found" });
90215
90769
  return;
90216
90770
  }
@@ -90235,7 +90789,7 @@ data: ${JSON.stringify({ error: msg })}
90235
90789
  this.json(res, 400, { error: "Not an image file" });
90236
90790
  return;
90237
90791
  }
90238
- const data = readFileSync29(resolved);
90792
+ const data = readFileSync31(resolved);
90239
90793
  res.writeHead(200, {
90240
90794
  "Content-Type": mime,
90241
90795
  "Content-Length": data.length,
@@ -90255,26 +90809,26 @@ data: ${JSON.stringify({ error: msg })}
90255
90809
  return;
90256
90810
  }
90257
90811
  try {
90258
- const { resolve: resolve21, dirname: dirname13 } = await import("node:path");
90259
- const { existsSync: existsSync40, statSync: statSync8 } = await import("node:fs");
90812
+ const { resolve: resolve21, dirname: dirname15 } = await import("node:path");
90813
+ const { existsSync: existsSync42, statSync: statSync8 } = await import("node:fs");
90260
90814
  const { exec: exec2 } = await import("node:child_process");
90261
- const { homedir: homedir23 } = await import("node:os");
90262
- const home = homedir23();
90815
+ const { homedir: homedir25 } = await import("node:os");
90816
+ const home = homedir25();
90263
90817
  const expanded = filePath.startsWith("~/") ? resolve21(home, filePath.slice(2)) : filePath === "~" ? home : filePath;
90264
90818
  const resolved = resolve21(expanded);
90265
- if (!existsSync40(resolved)) {
90819
+ if (!existsSync42(resolved)) {
90266
90820
  this.json(res, 404, { error: "Path not found" });
90267
90821
  return;
90268
90822
  }
90269
90823
  const isDir = statSync8(resolved).isDirectory();
90270
- const platform4 = process.platform;
90824
+ const platform5 = process.platform;
90271
90825
  let cmd;
90272
- if (platform4 === "darwin") {
90826
+ if (platform5 === "darwin") {
90273
90827
  cmd = isDir ? `open "${resolved}"` : `open -R "${resolved}"`;
90274
- } else if (platform4 === "win32") {
90828
+ } else if (platform5 === "win32") {
90275
90829
  cmd = isDir ? `explorer "${resolved}"` : `explorer /select,"${resolved}"`;
90276
90830
  } else {
90277
- cmd = `xdg-open "${isDir ? resolved : dirname13(resolved)}"`;
90831
+ cmd = `xdg-open "${isDir ? resolved : dirname15(resolved)}"`;
90278
90832
  }
90279
90833
  exec2(cmd, (err) => {
90280
90834
  if (err) {
@@ -90971,6 +91525,7 @@ data: ${JSON.stringify({ error: msg })}
90971
91525
  return [
90972
91526
  // ── Auth ─────────────────────────────────────────────────────────────
90973
91527
  exact("/api/auth/login", "POST"),
91528
+ exact("/api/auth/hub-login", "POST"),
90974
91529
  exact("/api/auth/logout", "POST"),
90975
91530
  exact("/api/auth/me", "GET"),
90976
91531
  exact("/api/auth/change-password", "POST"),
@@ -91130,6 +91685,13 @@ data: ${JSON.stringify({ error: msg })}
91130
91685
  exact("/api/models/validate-key", "POST"),
91131
91686
  regex(/^\/api\/models\/live\/[^/]+$/, "GET"),
91132
91687
  // ── Settings ─────────────────────────────────────────────────────────
91688
+ exact("/api/license", "GET"),
91689
+ exact("/api/license/refresh", "POST"),
91690
+ exact("/api/license/activate", "POST"),
91691
+ exact("/api/license/trial", "POST"),
91692
+ exact("/api/license/import", "POST"),
91693
+ exact("/api/license/deactivate", "POST"),
91694
+ exact("/api/settings/telemetry", "GET", "POST"),
91133
91695
  exact("/api/settings/hub", "GET"),
91134
91696
  exact("/api/settings/hub-token", "POST"),
91135
91697
  exact("/api/settings/llm", "GET", "POST"),
@@ -92278,17 +92840,10 @@ var init_billing_service = __esm({
92278
92840
  DEFAULT_PLANS = {
92279
92841
  free: {
92280
92842
  maxAgents: -1,
92281
- maxTokensPerMonth: 1e5,
92282
- maxToolCallsPerDay: 100,
92283
- maxMessagesPerDay: 50,
92284
- maxStorageBytes: 50 * 1024 * 1024
92285
- },
92286
- pro: {
92287
- maxAgents: 20,
92288
- maxTokensPerMonth: 5e6,
92289
- maxToolCallsPerDay: 5e3,
92290
- maxMessagesPerDay: 2e3,
92291
- maxStorageBytes: 5 * 1024 * 1024 * 1024
92843
+ maxTokensPerMonth: -1,
92844
+ maxToolCallsPerDay: 500,
92845
+ maxMessagesPerDay: -1,
92846
+ maxStorageBytes: -1
92292
92847
  },
92293
92848
  enterprise: {
92294
92849
  maxAgents: -1,
@@ -92304,6 +92859,10 @@ var init_billing_service = __esm({
92304
92859
  apiKeys = /* @__PURE__ */ new Map();
92305
92860
  apiKeysByKey = /* @__PURE__ */ new Map();
92306
92861
  orgPlans = /* @__PURE__ */ new Map();
92862
+ toolCallsTodayProvider;
92863
+ setToolCallsTodayProvider(fn) {
92864
+ this.toolCallsTodayProvider = fn;
92865
+ }
92307
92866
  setOrgPlan(orgId2, tier) {
92308
92867
  const plan = {
92309
92868
  orgId: orgId2,
@@ -92379,8 +92938,7 @@ var init_billing_service = __esm({
92379
92938
  }
92380
92939
  }
92381
92940
  if (type === "tool_call") {
92382
- const todayRecords = this.records.filter((r) => r.orgId === orgId2 && r.type === "tool_call" && r.timestamp.startsWith(today));
92383
- const todayCount = todayRecords.reduce((s2, r) => s2 + r.amount, 0);
92941
+ const todayCount = this.toolCallsTodayProvider ? this.toolCallsTodayProvider() : this.records.filter((r) => r.orgId === orgId2 && r.type === "tool_call" && r.timestamp.startsWith(today)).reduce((s2, r) => s2 + r.amount, 0);
92384
92942
  if (plan.limits.maxToolCallsPerDay > 0 && todayCount + additionalAmount > plan.limits.maxToolCallsPerDay) {
92385
92943
  return {
92386
92944
  allowed: false,
@@ -92508,13 +93066,523 @@ var init_billing_service = __esm({
92508
93066
  }
92509
93067
  });
92510
93068
 
93069
+ // ../org-manager/dist/license-service.js
93070
+ import { readFileSync as readFileSync23, writeFileSync as writeFileSync18, existsSync as existsSync30, mkdirSync as mkdirSync20 } from "node:fs";
93071
+ import { join as join25, dirname as dirname9 } from "node:path";
93072
+ import { homedir as homedir16 } from "node:os";
93073
+ import { randomUUID, createVerify } from "node:crypto";
93074
+ async function hubFetch(url, init, maxRedirects = 3) {
93075
+ let currentUrl = url;
93076
+ for (let i = 0; i <= maxRedirects; i++) {
93077
+ const res = await fetch(currentUrl, { ...init, redirect: "manual" });
93078
+ if (res.status >= 300 && res.status < 400) {
93079
+ const location = res.headers.get("location");
93080
+ if (!location)
93081
+ break;
93082
+ currentUrl = new URL(location, currentUrl).href;
93083
+ continue;
93084
+ }
93085
+ return res;
93086
+ }
93087
+ return fetch(currentUrl, init);
93088
+ }
93089
+ var log64, LICENSE_FILE, HEARTBEAT_INTERVAL_MS, HUB_LICENSE_PUBLIC_KEY, LicenseService;
93090
+ var init_license_service = __esm({
93091
+ "../org-manager/dist/license-service.js"() {
93092
+ "use strict";
93093
+ init_dist();
93094
+ log64 = createLogger("license");
93095
+ LICENSE_FILE = join25(homedir16(), ".markus", "license.json");
93096
+ HEARTBEAT_INTERVAL_MS = 4 * 60 * 60 * 1e3;
93097
+ HUB_LICENSE_PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
93098
+ MCowBQYDK2VwAyEAPlaceholderPublicKeyForOfflineLicenseVerification00=
93099
+ -----END PUBLIC KEY-----`;
93100
+ LicenseService = class {
93101
+ license;
93102
+ hubUrl;
93103
+ heartbeatTimer = null;
93104
+ constructor(hubUrl = "https://markus.global") {
93105
+ this.hubUrl = hubUrl;
93106
+ this.license = this.loadLicense();
93107
+ this.startHeartbeat();
93108
+ }
93109
+ loadLicense() {
93110
+ try {
93111
+ if (existsSync30(LICENSE_FILE)) {
93112
+ const data = JSON.parse(readFileSync23(LICENSE_FILE, "utf-8"));
93113
+ if (data && data.plan && data.instanceId) {
93114
+ return data;
93115
+ }
93116
+ }
93117
+ } catch (e) {
93118
+ log64.warn("Failed to load license file, using defaults");
93119
+ }
93120
+ const defaultLicense = {
93121
+ plan: "free",
93122
+ features: [],
93123
+ limits: { ...PLAN_LIMITS.free },
93124
+ instanceId: randomUUID()
93125
+ };
93126
+ this.saveLicense(defaultLicense);
93127
+ return defaultLicense;
93128
+ }
93129
+ saveLicense(license) {
93130
+ try {
93131
+ mkdirSync20(dirname9(LICENSE_FILE), { recursive: true });
93132
+ writeFileSync18(LICENSE_FILE, JSON.stringify(license, null, 2), "utf-8");
93133
+ } catch (e) {
93134
+ log64.warn("Failed to save license file");
93135
+ }
93136
+ }
93137
+ startHeartbeat() {
93138
+ if (this.heartbeatTimer)
93139
+ clearInterval(this.heartbeatTimer);
93140
+ this.heartbeatTimer = setInterval(() => {
93141
+ void this.sendHeartbeat();
93142
+ }, HEARTBEAT_INTERVAL_MS);
93143
+ setTimeout(() => void this.sendHeartbeat(), 3e4);
93144
+ }
93145
+ async sendHeartbeat() {
93146
+ if (!this.license.licenseKey)
93147
+ return;
93148
+ const hubToken = this.readHubToken();
93149
+ if (!hubToken)
93150
+ return;
93151
+ try {
93152
+ const res = await hubFetch(`${this.hubUrl}/api/licenses/heartbeat`, {
93153
+ method: "POST",
93154
+ headers: {
93155
+ "Content-Type": "application/json",
93156
+ "Authorization": `Bearer ${hubToken}`
93157
+ },
93158
+ body: JSON.stringify({
93159
+ licenseKey: this.license.licenseKey,
93160
+ instanceId: this.license.instanceId
93161
+ })
93162
+ });
93163
+ if (res.ok) {
93164
+ const data = await res.json();
93165
+ if (data.valid) {
93166
+ this.license.lastValidated = (/* @__PURE__ */ new Date()).toISOString();
93167
+ this.license.plan = data.plan;
93168
+ this.license.validUntil = data.validUntil;
93169
+ this.license.limits = { ...PLAN_LIMITS[data.plan] };
93170
+ this.license.features = data.plan === "enterprise" ? [...ENTERPRISE_FEATURES] : [];
93171
+ if (data.orgId)
93172
+ this.license.orgId = data.orgId;
93173
+ if (data.orgName)
93174
+ this.license.orgName = data.orgName;
93175
+ if (data.maxSeats !== null && data.maxSeats !== void 0)
93176
+ this.license.maxSeats = data.maxSeats;
93177
+ if (data.usedSeats !== null && data.usedSeats !== void 0)
93178
+ this.license.usedSeats = data.usedSeats;
93179
+ this.saveLicense(this.license);
93180
+ } else {
93181
+ log64.warn("License heartbeat returned invalid \u2014 reverting to free");
93182
+ this.revertToFree();
93183
+ }
93184
+ } else if (res.status === 403 || res.status === 404) {
93185
+ log64.warn("License heartbeat rejected \u2014 reverting to free");
93186
+ this.revertToFree();
93187
+ }
93188
+ } catch {
93189
+ log64.debug("License heartbeat failed (network issue) \u2014 using cached state");
93190
+ }
93191
+ }
93192
+ revertToFree() {
93193
+ this.license.plan = "free";
93194
+ this.license.licenseKey = void 0;
93195
+ this.license.validUntil = void 0;
93196
+ this.license.isTrial = void 0;
93197
+ this.license.isOffline = void 0;
93198
+ this.license.features = [];
93199
+ this.license.limits = { ...PLAN_LIMITS.free };
93200
+ this.license.orgId = void 0;
93201
+ this.license.orgName = void 0;
93202
+ this.license.maxSeats = void 0;
93203
+ this.license.usedSeats = void 0;
93204
+ this.saveLicense(this.license);
93205
+ }
93206
+ readHubToken() {
93207
+ try {
93208
+ const tokenPath = join25(homedir16(), ".markus", "hub-token");
93209
+ return existsSync30(tokenPath) ? readFileSync23(tokenPath, "utf-8").trim() : void 0;
93210
+ } catch {
93211
+ return void 0;
93212
+ }
93213
+ }
93214
+ // ─── Public API ────────────────────────────────────────────────────────
93215
+ getPlan() {
93216
+ if (this.license.validUntil && new Date(this.license.validUntil) < /* @__PURE__ */ new Date()) {
93217
+ if (this.license.plan !== "free") {
93218
+ log64.info("License expired \u2014 reverting to free");
93219
+ this.revertToFree();
93220
+ }
93221
+ }
93222
+ return this.license.plan;
93223
+ }
93224
+ getLimits() {
93225
+ this.getPlan();
93226
+ return { ...this.license.limits };
93227
+ }
93228
+ getFeatures() {
93229
+ this.getPlan();
93230
+ return [...this.license.features];
93231
+ }
93232
+ canUse(feature) {
93233
+ this.getPlan();
93234
+ if (this.license.plan === "enterprise")
93235
+ return true;
93236
+ return this.license.features.includes(feature);
93237
+ }
93238
+ getInfo() {
93239
+ this.getPlan();
93240
+ return { ...this.license };
93241
+ }
93242
+ getInstanceId() {
93243
+ return this.license.instanceId;
93244
+ }
93245
+ async activateLicense(licenseKey) {
93246
+ const hubToken = this.readHubToken();
93247
+ if (!hubToken) {
93248
+ return { success: false, error: "Not authenticated with Markus Hub" };
93249
+ }
93250
+ try {
93251
+ const res = await hubFetch(`${this.hubUrl}/api/licenses/activate`, {
93252
+ method: "POST",
93253
+ headers: {
93254
+ "Content-Type": "application/json",
93255
+ "Authorization": `Bearer ${hubToken}`
93256
+ },
93257
+ body: JSON.stringify({
93258
+ licenseKey,
93259
+ instanceId: this.license.instanceId
93260
+ })
93261
+ });
93262
+ const data = await res.json();
93263
+ if (res.ok && data.success) {
93264
+ this.license.licenseKey = licenseKey;
93265
+ this.license.plan = data.plan ?? "enterprise";
93266
+ this.license.validUntil = data.validUntil;
93267
+ this.license.isTrial = data.isTrial;
93268
+ this.license.isOffline = false;
93269
+ this.license.features = data.features ?? [...ENTERPRISE_FEATURES];
93270
+ this.license.limits = { ...PLAN_LIMITS[this.license.plan] };
93271
+ this.license.lastValidated = (/* @__PURE__ */ new Date()).toISOString();
93272
+ this.license.orgId = data.orgId;
93273
+ this.license.orgName = data.orgName;
93274
+ this.license.maxSeats = data.maxSeats;
93275
+ this.license.usedSeats = data.usedSeats;
93276
+ this.saveLicense(this.license);
93277
+ log64.info(`License activated: ${this.license.plan} (valid until ${this.license.validUntil})`);
93278
+ return { success: true };
93279
+ }
93280
+ return { success: false, error: data.error ?? "Activation failed" };
93281
+ } catch {
93282
+ return { success: false, error: "Could not connect to Markus Hub" };
93283
+ }
93284
+ }
93285
+ async activateTrial() {
93286
+ const hubToken = this.readHubToken();
93287
+ if (!hubToken) {
93288
+ return { success: false, error: "Not authenticated with Markus Hub" };
93289
+ }
93290
+ try {
93291
+ const res = await hubFetch(`${this.hubUrl}/api/licenses/trial`, {
93292
+ method: "POST",
93293
+ headers: {
93294
+ "Content-Type": "application/json",
93295
+ "Authorization": `Bearer ${hubToken}`
93296
+ },
93297
+ body: JSON.stringify({
93298
+ instanceId: this.license.instanceId
93299
+ })
93300
+ });
93301
+ const data = await res.json();
93302
+ if (res.ok && data.success && data.licenseKey) {
93303
+ this.license.licenseKey = data.licenseKey;
93304
+ this.license.plan = data.plan ?? "enterprise";
93305
+ this.license.validUntil = data.validUntil;
93306
+ this.license.isTrial = true;
93307
+ this.license.isOffline = false;
93308
+ this.license.features = [...ENTERPRISE_FEATURES];
93309
+ this.license.limits = { ...PLAN_LIMITS.enterprise };
93310
+ this.license.lastValidated = (/* @__PURE__ */ new Date()).toISOString();
93311
+ this.license.orgId = data.orgId;
93312
+ this.license.orgName = data.orgName;
93313
+ this.license.maxSeats = data.maxSeats;
93314
+ this.saveLicense(this.license);
93315
+ log64.info(`Trial activated (valid until ${this.license.validUntil})`);
93316
+ return { success: true };
93317
+ }
93318
+ return { success: false, error: data.error ?? "Trial activation failed" };
93319
+ } catch {
93320
+ return { success: false, error: "Could not connect to Markus Hub" };
93321
+ }
93322
+ }
93323
+ importOfflineLicense(fileContent) {
93324
+ try {
93325
+ const payload = JSON.parse(fileContent);
93326
+ if (payload.version !== 1 || payload.plan !== "enterprise") {
93327
+ return { success: false, error: "Invalid license file format" };
93328
+ }
93329
+ if (new Date(payload.validUntil) < /* @__PURE__ */ new Date()) {
93330
+ return { success: false, error: "License has expired" };
93331
+ }
93332
+ if (payload.signature) {
93333
+ try {
93334
+ const verifier = createVerify("Ed25519");
93335
+ const signData = JSON.stringify({
93336
+ version: payload.version,
93337
+ licenseId: payload.licenseId,
93338
+ plan: payload.plan,
93339
+ issuedTo: payload.issuedTo,
93340
+ validFrom: payload.validFrom,
93341
+ validUntil: payload.validUntil,
93342
+ maxInstances: payload.maxInstances,
93343
+ features: payload.features
93344
+ });
93345
+ verifier.update(signData);
93346
+ const valid = verifier.verify(HUB_LICENSE_PUBLIC_KEY, payload.signature, "base64");
93347
+ if (!valid) {
93348
+ log64.warn("Offline license signature verification failed \u2014 accepting in dev mode");
93349
+ }
93350
+ } catch {
93351
+ log64.warn("Offline license signature verification skipped (key format)");
93352
+ }
93353
+ }
93354
+ this.license.licenseKey = payload.licenseId;
93355
+ this.license.plan = "enterprise";
93356
+ this.license.validUntil = payload.validUntil;
93357
+ this.license.isTrial = false;
93358
+ this.license.isOffline = true;
93359
+ this.license.features = payload.features ?? [...ENTERPRISE_FEATURES];
93360
+ this.license.limits = { ...PLAN_LIMITS.enterprise };
93361
+ this.license.lastValidated = (/* @__PURE__ */ new Date()).toISOString();
93362
+ this.saveLicense(this.license);
93363
+ log64.info(`Offline license imported: ${payload.licenseId} (valid until ${payload.validUntil})`);
93364
+ return { success: true };
93365
+ } catch {
93366
+ return { success: false, error: "Could not parse license file" };
93367
+ }
93368
+ }
93369
+ async deactivate() {
93370
+ if (this.license.licenseKey && !this.license.isOffline) {
93371
+ const hubToken = this.readHubToken();
93372
+ if (hubToken) {
93373
+ try {
93374
+ await hubFetch(`${this.hubUrl}/api/licenses/deactivate`, {
93375
+ method: "POST",
93376
+ headers: {
93377
+ "Content-Type": "application/json",
93378
+ "Authorization": `Bearer ${hubToken}`
93379
+ },
93380
+ body: JSON.stringify({
93381
+ licenseKey: this.license.licenseKey,
93382
+ instanceId: this.license.instanceId
93383
+ })
93384
+ });
93385
+ } catch {
93386
+ }
93387
+ }
93388
+ }
93389
+ this.revertToFree();
93390
+ log64.info("License deactivated");
93391
+ }
93392
+ async revalidate() {
93393
+ if (!this.license.licenseKey) {
93394
+ const saved = this.loadLicense();
93395
+ if (saved.licenseKey) {
93396
+ this.license = saved;
93397
+ }
93398
+ }
93399
+ await this.syncFromHub();
93400
+ if (this.license.licenseKey) {
93401
+ await this.sendHeartbeat();
93402
+ }
93403
+ this.getPlan();
93404
+ return { ...this.license };
93405
+ }
93406
+ async syncFromHub() {
93407
+ const hubToken = this.readHubToken();
93408
+ if (!hubToken)
93409
+ return;
93410
+ try {
93411
+ const res = await hubFetch(`${this.hubUrl}/api/licenses/mine`, {
93412
+ headers: { "Authorization": `Bearer ${hubToken}` }
93413
+ });
93414
+ if (!res.ok)
93415
+ return;
93416
+ const data = await res.json();
93417
+ if (!data.license)
93418
+ return;
93419
+ const currentKey = this.license.licenseKey;
93420
+ if (currentKey === data.license.licenseKey) {
93421
+ if (data.license.usedSeats !== null && data.license.usedSeats !== void 0 && data.license.usedSeats !== this.license.usedSeats) {
93422
+ this.license.usedSeats = data.license.usedSeats;
93423
+ this.saveLicense(this.license);
93424
+ }
93425
+ return;
93426
+ }
93427
+ const currentIsTrial = this.license.isTrial;
93428
+ const newIsBetter = !currentKey || currentIsTrial && !data.license.isTrial || !currentIsTrial && !data.license.isTrial && new Date(data.license.validUntil) > new Date(this.license.validUntil ?? "");
93429
+ if (!newIsBetter)
93430
+ return;
93431
+ log64.info(`Found better license on Hub: ${data.license.licenseKey} (current: ${currentKey ?? "none"})`);
93432
+ const result = await this.activateLicense(data.license.licenseKey);
93433
+ if (result.success) {
93434
+ if (data.license.orgId)
93435
+ this.license.orgId = data.license.orgId;
93436
+ if (data.license.orgName)
93437
+ this.license.orgName = data.license.orgName;
93438
+ if (data.license.maxSeats !== null && data.license.maxSeats !== void 0)
93439
+ this.license.maxSeats = data.license.maxSeats;
93440
+ this.saveLicense(this.license);
93441
+ log64.info(`Upgraded license from Hub: ${data.license.licenseKey}`);
93442
+ }
93443
+ } catch {
93444
+ log64.debug("Failed to sync license from Hub");
93445
+ }
93446
+ }
93447
+ setHubUrl(url) {
93448
+ this.hubUrl = url;
93449
+ }
93450
+ destroy() {
93451
+ if (this.heartbeatTimer) {
93452
+ clearInterval(this.heartbeatTimer);
93453
+ this.heartbeatTimer = null;
93454
+ }
93455
+ }
93456
+ };
93457
+ }
93458
+ });
93459
+
93460
+ // ../org-manager/dist/telemetry-service.js
93461
+ import { readFileSync as readFileSync24, writeFileSync as writeFileSync19, existsSync as existsSync31, mkdirSync as mkdirSync21 } from "node:fs";
93462
+ import { join as join26, dirname as dirname10 } from "node:path";
93463
+ import { homedir as homedir17, platform as platform3, arch as arch2 } from "node:os";
93464
+ async function hubFetch2(url, init) {
93465
+ let currentUrl = url;
93466
+ for (let i = 0; i < 3; i++) {
93467
+ const res = await fetch(currentUrl, { ...init, redirect: "manual" });
93468
+ if (res.status >= 300 && res.status < 400) {
93469
+ const location = res.headers.get("location");
93470
+ if (!location)
93471
+ return res;
93472
+ currentUrl = new URL(location, currentUrl).href;
93473
+ continue;
93474
+ }
93475
+ return res;
93476
+ }
93477
+ return fetch(currentUrl, init);
93478
+ }
93479
+ var log65, TELEMETRY_CONFIG_FILE, REPORT_INTERVAL_MS, TelemetryService;
93480
+ var init_telemetry_service = __esm({
93481
+ "../org-manager/dist/telemetry-service.js"() {
93482
+ "use strict";
93483
+ init_dist();
93484
+ log65 = createLogger("telemetry");
93485
+ TELEMETRY_CONFIG_FILE = join26(homedir17(), ".markus", "telemetry.json");
93486
+ REPORT_INTERVAL_MS = 6 * 60 * 60 * 1e3;
93487
+ TelemetryService = class {
93488
+ enabled;
93489
+ hubUrl;
93490
+ instanceId;
93491
+ timer = null;
93492
+ statsProvider = null;
93493
+ constructor(hubUrl, instanceId) {
93494
+ this.hubUrl = hubUrl;
93495
+ this.instanceId = instanceId;
93496
+ const config = this.loadConfig();
93497
+ this.enabled = config.enabled;
93498
+ }
93499
+ loadConfig() {
93500
+ try {
93501
+ if (existsSync31(TELEMETRY_CONFIG_FILE)) {
93502
+ return JSON.parse(readFileSync24(TELEMETRY_CONFIG_FILE, "utf-8"));
93503
+ }
93504
+ } catch {
93505
+ }
93506
+ return { enabled: true };
93507
+ }
93508
+ saveConfig(config) {
93509
+ try {
93510
+ mkdirSync21(dirname10(TELEMETRY_CONFIG_FILE), { recursive: true });
93511
+ writeFileSync19(TELEMETRY_CONFIG_FILE, JSON.stringify(config, null, 2), "utf-8");
93512
+ } catch {
93513
+ }
93514
+ }
93515
+ setEnabled(enabled) {
93516
+ this.enabled = enabled;
93517
+ const config = this.loadConfig();
93518
+ config.enabled = enabled;
93519
+ this.saveConfig(config);
93520
+ log65.info(`Telemetry ${enabled ? "enabled" : "disabled"}`);
93521
+ }
93522
+ isEnabled() {
93523
+ return this.enabled;
93524
+ }
93525
+ setStatsProvider(provider) {
93526
+ this.statsProvider = provider;
93527
+ }
93528
+ start() {
93529
+ if (this.timer)
93530
+ return;
93531
+ this.timer = setInterval(() => void this.report(), REPORT_INTERVAL_MS);
93532
+ setTimeout(() => void this.report(), 6e4);
93533
+ }
93534
+ async report() {
93535
+ if (!this.enabled || !this.statsProvider)
93536
+ return;
93537
+ try {
93538
+ const stats = this.statsProvider();
93539
+ const payload = {
93540
+ instanceId: this.instanceId,
93541
+ version: APP_VERSION,
93542
+ os: `${platform3()}/${arch2()}`,
93543
+ ...stats
93544
+ };
93545
+ const hubToken = this.readHubToken();
93546
+ const headers = { "Content-Type": "application/json" };
93547
+ if (hubToken)
93548
+ headers["Authorization"] = `Bearer ${hubToken}`;
93549
+ await hubFetch2(`${this.hubUrl}/api/telemetry`, {
93550
+ method: "POST",
93551
+ headers,
93552
+ body: JSON.stringify(payload)
93553
+ });
93554
+ const config = this.loadConfig();
93555
+ config.lastReportAt = (/* @__PURE__ */ new Date()).toISOString();
93556
+ this.saveConfig(config);
93557
+ } catch {
93558
+ log65.debug("Telemetry report failed (network)");
93559
+ }
93560
+ }
93561
+ readHubToken() {
93562
+ try {
93563
+ const tokenPath = join26(homedir17(), ".markus", "hub-token");
93564
+ return existsSync31(tokenPath) ? readFileSync24(tokenPath, "utf-8").trim() : void 0;
93565
+ } catch {
93566
+ return void 0;
93567
+ }
93568
+ }
93569
+ destroy() {
93570
+ if (this.timer) {
93571
+ clearInterval(this.timer);
93572
+ this.timer = null;
93573
+ }
93574
+ }
93575
+ };
93576
+ }
93577
+ });
93578
+
92511
93579
  // ../org-manager/dist/audit-service.js
92512
- var log64, entryCounter, AuditService;
93580
+ var log66, entryCounter, AuditService;
92513
93581
  var init_audit_service = __esm({
92514
93582
  "../org-manager/dist/audit-service.js"() {
92515
93583
  "use strict";
92516
93584
  init_dist();
92517
- log64 = createLogger("audit");
93585
+ log66 = createLogger("audit");
92518
93586
  entryCounter = 0;
92519
93587
  AuditService = class {
92520
93588
  entries = [];
@@ -92522,7 +93590,7 @@ var init_audit_service = __esm({
92522
93590
  db;
92523
93591
  setRepository(db) {
92524
93592
  this.db = db;
92525
- log64.info("Audit persistence enabled \u2014 events will be written to DB");
93593
+ log66.info("Audit persistence enabled \u2014 events will be written to DB");
92526
93594
  }
92527
93595
  record(entry) {
92528
93596
  const full = {
@@ -92548,7 +93616,7 @@ var init_audit_service = __esm({
92548
93616
  durationMs: full.durationMs,
92549
93617
  success: full.success,
92550
93618
  createdAt: new Date(full.timestamp)
92551
- }).catch((err) => log64.warn("Failed to persist audit entry", { id: full.id, error: String(err) }));
93619
+ }).catch((err) => log66.warn("Failed to persist audit entry", { id: full.id, error: String(err) }));
92552
93620
  }
92553
93621
  return full;
92554
93622
  }
@@ -92631,12 +93699,12 @@ var init_audit_service = __esm({
92631
93699
  });
92632
93700
 
92633
93701
  // ../org-manager/dist/project-service.js
92634
- var log65, ProjectService;
93702
+ var log67, ProjectService;
92635
93703
  var init_project_service = __esm({
92636
93704
  "../org-manager/dist/project-service.js"() {
92637
93705
  "use strict";
92638
93706
  init_dist();
92639
- log65 = createLogger("project-service");
93707
+ log67 = createLogger("project-service");
92640
93708
  ProjectService = class {
92641
93709
  projects = /* @__PURE__ */ new Map();
92642
93710
  projectRepo;
@@ -92666,9 +93734,9 @@ var init_project_service = __esm({
92666
93734
  };
92667
93735
  this.projects.set(project.id, project);
92668
93736
  }
92669
- log65.info(`Loaded ${this.projects.size} projects from DB`);
93737
+ log67.info(`Loaded ${this.projects.size} projects from DB`);
92670
93738
  } catch (err) {
92671
- log65.warn("Failed to load projects from DB", { error: String(err) });
93739
+ log67.warn("Failed to load projects from DB", { error: String(err) });
92672
93740
  }
92673
93741
  }
92674
93742
  }
@@ -92705,8 +93773,8 @@ var init_project_service = __esm({
92705
93773
  reportSchedule: project.reportSchedule,
92706
93774
  onboardingConfig: project.onboardingConfig,
92707
93775
  createdBy: opts.createdBy
92708
- }).catch((err) => log65.warn("Failed to persist project", { error: String(err) }));
92709
- log65.info("Project created", { id: project.id, name: project.name });
93776
+ }).catch((err) => log67.warn("Failed to persist project", { error: String(err) }));
93777
+ log67.info("Project created", { id: project.id, name: project.name });
92710
93778
  return project;
92711
93779
  }
92712
93780
  getProject(id) {
@@ -92721,14 +93789,14 @@ var init_project_service = __esm({
92721
93789
  if (!project)
92722
93790
  throw new Error(`Project not found: ${id}`);
92723
93791
  Object.assign(project, updates, { updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
92724
- this.projectRepo?.update(id, updates).catch((err) => log65.warn("Failed to persist project update", { error: String(err) }));
92725
- log65.info("Project updated", { id });
93792
+ this.projectRepo?.update(id, updates).catch((err) => log67.warn("Failed to persist project update", { error: String(err) }));
93793
+ log67.info("Project updated", { id });
92726
93794
  return project;
92727
93795
  }
92728
93796
  deleteProject(id) {
92729
93797
  this.projects.delete(id);
92730
- this.projectRepo?.delete(id).catch((err) => log65.warn("Failed to delete project from DB", { error: String(err) }));
92731
- log65.info("Project deleted", { id });
93798
+ this.projectRepo?.delete(id).catch((err) => log67.warn("Failed to delete project from DB", { error: String(err) }));
93799
+ log67.info("Project deleted", { id });
92732
93800
  }
92733
93801
  // ─── Agent Onboarding ──────────────────────────────────────────────────────
92734
93802
  async onboardAgent(agentId2, projectId) {
@@ -92753,7 +93821,7 @@ var init_project_service = __esm({
92753
93821
  parts.push(`- Max pending tasks per agent: ${project.governancePolicy.maxPendingTasksPerAgent}`);
92754
93822
  }
92755
93823
  const onboardingDoc = parts.join("\n");
92756
- log65.info("Agent onboarded to project", { agentId: agentId2, projectId });
93824
+ log67.info("Agent onboarded to project", { agentId: agentId2, projectId });
92757
93825
  return onboardingDoc;
92758
93826
  }
92759
93827
  };
@@ -92761,12 +93829,12 @@ var init_project_service = __esm({
92761
93829
  });
92762
93830
 
92763
93831
  // ../org-manager/dist/requirement-service.js
92764
- var log66, RequirementService;
93832
+ var log68, RequirementService;
92765
93833
  var init_requirement_service = __esm({
92766
93834
  "../org-manager/dist/requirement-service.js"() {
92767
93835
  "use strict";
92768
93836
  init_dist();
92769
- log66 = createLogger("requirement-service");
93837
+ log68 = createLogger("requirement-service");
92770
93838
  RequirementService = class _RequirementService {
92771
93839
  requirements = /* @__PURE__ */ new Map();
92772
93840
  requirementRepo;
@@ -92847,7 +93915,7 @@ var init_requirement_service = __esm({
92847
93915
  reason: reason ?? null
92848
93916
  });
92849
93917
  } catch (e) {
92850
- log66.warn("Failed to record requirement status transition", { reqId, from, to, error: String(e) });
93918
+ log68.warn("Failed to record requirement status transition", { reqId, from, to, error: String(e) });
92851
93919
  }
92852
93920
  }
92853
93921
  getRequirementStatusHistory(reqId, limit = 50) {
@@ -92927,7 +93995,7 @@ var init_requirement_service = __esm({
92927
93995
  approvedBy: req.approvedBy ?? void 0,
92928
93996
  approvedAt: req.approvedAt ? new Date(req.approvedAt) : void 0,
92929
93997
  tags: req.tags
92930
- }).catch((e) => log66.error("Failed to persist requirement", { id: req.id, error: String(e) }));
93998
+ }).catch((e) => log68.error("Failed to persist requirement", { id: req.id, error: String(e) }));
92931
93999
  }
92932
94000
  this.broadcast("requirement:created", req);
92933
94001
  if (this.hitlService && req.source === "agent") {
@@ -92950,10 +94018,10 @@ var init_requirement_service = __esm({
92950
94018
  this.rejectRequirement(req.id, result.respondedBy ?? "hitl", result.comment || "Rejected via approval");
92951
94019
  }
92952
94020
  }).catch((err) => {
92953
- log66.error("HITL approval flow error for requirement", { requirementId: req.id, error: String(err) });
94021
+ log68.error("HITL approval flow error for requirement", { requirementId: req.id, error: String(err) });
92954
94022
  });
92955
94023
  }
92956
- log66.info("Requirement created", {
94024
+ log68.info("Requirement created", {
92957
94025
  id: req.id,
92958
94026
  source: req.source,
92959
94027
  status: req.status,
@@ -92996,11 +94064,11 @@ var init_requirement_service = __esm({
92996
94064
  req.approvedAt = now3;
92997
94065
  req.updatedAt = now3;
92998
94066
  if (this.requirementRepo) {
92999
- this.requirementRepo.approve(id, userId2).catch((e) => log66.error("Failed to persist requirement approval", { id, error: String(e) }));
94067
+ this.requirementRepo.approve(id, userId2).catch((e) => log68.error("Failed to persist requirement approval", { id, error: String(e) }));
93000
94068
  }
93001
94069
  this.recordTransition(id, oldStatus, "in_progress", userId2, "human", "Approved");
93002
94070
  this.broadcast("requirement:approved", req);
93003
- log66.info("Requirement approved", { id, approvedBy: userId2 });
94071
+ log68.info("Requirement approved", { id, approvedBy: userId2 });
93004
94072
  this.notifyCreatorOnDecision(req, "approved", userId2);
93005
94073
  return req;
93006
94074
  }
@@ -93027,11 +94095,11 @@ var init_requirement_service = __esm({
93027
94095
  req.rejectedBy = userId2;
93028
94096
  req.updatedAt = now3;
93029
94097
  if (this.requirementRepo) {
93030
- this.requirementRepo.reject(id, reason, userId2).catch((e) => log66.error("Failed to persist requirement rejection", { id, error: String(e) }));
94098
+ this.requirementRepo.reject(id, reason, userId2).catch((e) => log68.error("Failed to persist requirement rejection", { id, error: String(e) }));
93031
94099
  }
93032
94100
  this.recordTransition(id, oldStatus, "rejected", userId2, "human", reason);
93033
94101
  this.broadcast("requirement:rejected", req);
93034
- log66.info("Requirement rejected", { id, reason });
94102
+ log68.info("Requirement rejected", { id, reason });
93035
94103
  this.notifyCreatorOnDecision(req, "rejected", userId2, reason);
93036
94104
  return req;
93037
94105
  }
@@ -93064,7 +94132,7 @@ var init_requirement_service = __esm({
93064
94132
  req.updatedAt = now3;
93065
94133
  this.recordTransition(id, oldStatus, "pending", req.createdBy, "agent", "Resubmitted");
93066
94134
  if (this.requirementRepo) {
93067
- const persistErr = (e) => log66.error("Failed to persist requirement resubmission", { id, error: String(e) });
94135
+ const persistErr = (e) => log68.error("Failed to persist requirement resubmission", { id, error: String(e) });
93068
94136
  this.requirementRepo.updateStatus(id, "pending").catch(persistErr);
93069
94137
  this.requirementRepo.clearRejectionMetadata(id).catch(persistErr);
93070
94138
  if (updates) {
@@ -93072,7 +94140,7 @@ var init_requirement_service = __esm({
93072
94140
  }
93073
94141
  }
93074
94142
  this.broadcast("requirement:resubmitted", req);
93075
- log66.info("Requirement resubmitted for review", { id, hasUpdates: !!updates });
94143
+ log68.info("Requirement resubmitted for review", { id, hasUpdates: !!updates });
93076
94144
  if (this.hitlService && req.source === "agent") {
93077
94145
  const creatorName = this.resolveAgentName(req.createdBy);
93078
94146
  this.hitlService.requestApprovalAndWait({
@@ -93093,7 +94161,7 @@ var init_requirement_service = __esm({
93093
94161
  this.rejectRequirement(req.id, result.respondedBy ?? "hitl", result.comment || "Rejected via approval");
93094
94162
  }
93095
94163
  }).catch((err) => {
93096
- log66.error("HITL approval flow error for resubmitted requirement", { requirementId: req.id, error: String(err) });
94164
+ log68.error("HITL approval flow error for resubmitted requirement", { requirementId: req.id, error: String(err) });
93097
94165
  });
93098
94166
  }
93099
94167
  return req;
@@ -93129,7 +94197,7 @@ var init_requirement_service = __esm({
93129
94197
  req.rejectedBy = void 0;
93130
94198
  }
93131
94199
  if (this.requirementRepo) {
93132
- const persistErr = (e) => log66.error("Failed to persist requirement status update", { id, error: String(e) });
94200
+ const persistErr = (e) => log68.error("Failed to persist requirement status update", { id, error: String(e) });
93133
94201
  if (newStatus === "in_progress" && oldStatus === "pending") {
93134
94202
  this.requirementRepo.approve(id, req.approvedBy ?? "unknown").catch(persistErr);
93135
94203
  } else if (newStatus === "rejected") {
@@ -93160,7 +94228,7 @@ var init_requirement_service = __esm({
93160
94228
  const resolvedActorType = actorType ?? (userId2 ? "human" : "system");
93161
94229
  this.recordTransition(id, oldStatus, newStatus, userId2, resolvedActorType);
93162
94230
  this.broadcast("requirement:updated", req);
93163
- log66.info("Requirement status updated", { id, from: oldStatus, to: newStatus });
94231
+ log68.info("Requirement status updated", { id, from: oldStatus, to: newStatus });
93164
94232
  return req;
93165
94233
  }
93166
94234
  /**
@@ -93180,7 +94248,7 @@ var init_requirement_service = __esm({
93180
94248
  req.tags = data.tags;
93181
94249
  req.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
93182
94250
  if (this.requirementRepo) {
93183
- this.requirementRepo.update(id, data).catch((e) => log66.error("Failed to persist requirement update", { id, error: String(e) }));
94251
+ this.requirementRepo.update(id, data).catch((e) => log68.error("Failed to persist requirement update", { id, error: String(e) }));
93184
94252
  }
93185
94253
  this.broadcast("requirement:updated", req);
93186
94254
  return req;
@@ -93262,11 +94330,11 @@ var init_requirement_service = __esm({
93262
94330
  req.status = "cancelled";
93263
94331
  req.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
93264
94332
  if (this.requirementRepo) {
93265
- this.requirementRepo.updateStatus(id, "cancelled").catch((e) => log66.error("Failed to persist requirement cancellation", { id, error: String(e) }));
94333
+ this.requirementRepo.updateStatus(id, "cancelled").catch((e) => log68.error("Failed to persist requirement cancellation", { id, error: String(e) }));
93266
94334
  }
93267
94335
  this.recordTransition(id, oldStatus, "cancelled", cancelledBy, cancelledByType ?? "system", "Cancelled");
93268
94336
  this.broadcast("requirement:cancelled", req);
93269
- log66.info("Requirement cancelled", { id });
94337
+ log68.info("Requirement cancelled", { id });
93270
94338
  return req;
93271
94339
  }
93272
94340
  getRequirement(id) {
@@ -93323,9 +94391,9 @@ var init_requirement_service = __esm({
93323
94391
  };
93324
94392
  this.requirements.set(req.id, req);
93325
94393
  }
93326
- log66.info("Loaded requirements from storage", { orgId: orgId2, count: rows.length });
94394
+ log68.info("Loaded requirements from storage", { orgId: orgId2, count: rows.length });
93327
94395
  } catch (e) {
93328
- log66.error("Failed to load requirements from storage", { orgId: orgId2, error: String(e) });
94396
+ log68.error("Failed to load requirements from storage", { orgId: orgId2, error: String(e) });
93329
94397
  }
93330
94398
  }
93331
94399
  /**
@@ -93347,7 +94415,7 @@ var init_requirement_service = __esm({
93347
94415
  }
93348
94416
  const linked = [...this.requirements.values()].filter((r) => r.taskIds.length > 0).length;
93349
94417
  if (linked > 0) {
93350
- log66.info("Rebuilt requirement-task links", { linkedRequirements: linked });
94418
+ log68.info("Rebuilt requirement-task links", { linkedRequirements: linked });
93351
94419
  }
93352
94420
  }
93353
94421
  deleteRequirement(id) {
@@ -93356,7 +94424,7 @@ var init_requirement_service = __esm({
93356
94424
  }
93357
94425
  this.requirements.delete(id);
93358
94426
  if (this.requirementRepo) {
93359
- this.requirementRepo.delete(id).catch((e) => log66.error("Failed to delete requirement from storage", { id, error: String(e) }));
94427
+ this.requirementRepo.delete(id).catch((e) => log68.error("Failed to delete requirement from storage", { id, error: String(e) }));
93360
94428
  }
93361
94429
  }
93362
94430
  /**
@@ -93405,7 +94473,7 @@ var init_requirement_service = __esm({
93405
94473
  priority: 1,
93406
94474
  metadata: { senderName: "System", senderRole: "manager" }
93407
94475
  });
93408
- log66.info("Notified creator agent about requirement decision", {
94476
+ log68.info("Notified creator agent about requirement decision", {
93409
94477
  requirementId: req.id,
93410
94478
  creatorId,
93411
94479
  decision
@@ -93450,12 +94518,12 @@ var init_requirement_service = __esm({
93450
94518
  });
93451
94519
 
93452
94520
  // ../org-manager/dist/knowledge-service.js
93453
- var log67, KnowledgeService;
94521
+ var log69, KnowledgeService;
93454
94522
  var init_knowledge_service = __esm({
93455
94523
  "../org-manager/dist/knowledge-service.js"() {
93456
94524
  "use strict";
93457
94525
  init_dist();
93458
- log67 = createLogger("knowledge-service");
94526
+ log69 = createLogger("knowledge-service");
93459
94527
  KnowledgeService = class {
93460
94528
  entries = /* @__PURE__ */ new Map();
93461
94529
  fileStore;
@@ -93465,7 +94533,7 @@ var init_knowledge_service = __esm({
93465
94533
  for (const entry of fileStore.loadAll()) {
93466
94534
  this.entries.set(entry.id, entry);
93467
94535
  }
93468
- log67.info("Knowledge loaded from file store", { count: this.entries.size });
94536
+ log69.info("Knowledge loaded from file store", { count: this.entries.size });
93469
94537
  }
93470
94538
  }
93471
94539
  /** Returns the absolute file path of a knowledge entry (for agent file_read). */
@@ -93510,7 +94578,7 @@ var init_knowledge_service = __esm({
93510
94578
  }
93511
94579
  }
93512
94580
  this.persistScope(entry.scope, entry.scopeId);
93513
- log67.info("Knowledge contributed", {
94581
+ log69.info("Knowledge contributed", {
93514
94582
  id: entry.id,
93515
94583
  scope: entry.scope,
93516
94584
  category: entry.category,
@@ -93588,7 +94656,7 @@ var init_knowledge_service = __esm({
93588
94656
  entry.status = "outdated";
93589
94657
  entry.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
93590
94658
  this.persistScope(entry.scope, entry.scopeId);
93591
- log67.info("Knowledge flagged as outdated", { id, reason });
94659
+ log69.info("Knowledge flagged as outdated", { id, reason });
93592
94660
  }
93593
94661
  flagDisputed(id, reason) {
93594
94662
  const entry = this.entries.get(id);
@@ -93597,7 +94665,7 @@ var init_knowledge_service = __esm({
93597
94665
  entry.status = "disputed";
93598
94666
  entry.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
93599
94667
  this.persistScope(entry.scope, entry.scopeId);
93600
- log67.info("Knowledge flagged as disputed", { id, reason });
94668
+ log69.info("Knowledge flagged as disputed", { id, reason });
93601
94669
  }
93602
94670
  verify(id, verifiedBy) {
93603
94671
  const entry = this.entries.get(id);
@@ -93607,7 +94675,7 @@ var init_knowledge_service = __esm({
93607
94675
  entry.verifiedBy = verifiedBy;
93608
94676
  entry.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
93609
94677
  this.persistScope(entry.scope, entry.scopeId);
93610
- log67.info("Knowledge verified", { id, verifiedBy });
94678
+ log69.info("Knowledge verified", { id, verifiedBy });
93611
94679
  }
93612
94680
  // ─── Metrics ───────────────────────────────────────────────────────────────
93613
94681
  getContributions(scopeId, periodStart, periodEnd) {
@@ -93631,8 +94699,8 @@ var init_knowledge_service = __esm({
93631
94699
  });
93632
94700
 
93633
94701
  // ../org-manager/dist/file-knowledge-store.js
93634
- import { readFileSync as readFileSync23, writeFileSync as writeFileSync18, existsSync as existsSync30, mkdirSync as mkdirSync20, readdirSync as readdirSync12, unlinkSync as unlinkSync3 } from "node:fs";
93635
- import { join as join25 } from "node:path";
94702
+ import { readFileSync as readFileSync25, writeFileSync as writeFileSync20, existsSync as existsSync32, mkdirSync as mkdirSync22, readdirSync as readdirSync12, unlinkSync as unlinkSync3 } from "node:fs";
94703
+ import { join as join27 } from "node:path";
93636
94704
  function readdirSafe(dir) {
93637
94705
  try {
93638
94706
  return readdirSync12(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
@@ -93640,62 +94708,62 @@ function readdirSafe(dir) {
93640
94708
  return [];
93641
94709
  }
93642
94710
  }
93643
- var log68, FileKnowledgeStore;
94711
+ var log70, FileKnowledgeStore;
93644
94712
  var init_file_knowledge_store = __esm({
93645
94713
  "../org-manager/dist/file-knowledge-store.js"() {
93646
94714
  "use strict";
93647
94715
  init_dist();
93648
- log68 = createLogger("file-knowledge-store");
94716
+ log70 = createLogger("file-knowledge-store");
93649
94717
  FileKnowledgeStore = class {
93650
94718
  baseDir;
93651
94719
  constructor(baseDir) {
93652
94720
  this.baseDir = baseDir;
93653
- mkdirSync20(baseDir, { recursive: true });
94721
+ mkdirSync22(baseDir, { recursive: true });
93654
94722
  }
93655
94723
  scopeDir(scope, scopeId) {
93656
- return join25(this.baseDir, scope, scopeId);
94724
+ return join27(this.baseDir, scope, scopeId);
93657
94725
  }
93658
94726
  indexPath(scope, scopeId) {
93659
- return join25(this.scopeDir(scope, scopeId), "_index.json");
94727
+ return join27(this.scopeDir(scope, scopeId), "_index.json");
93660
94728
  }
93661
94729
  entryPath(entry) {
93662
- return join25(this.scopeDir(entry.scope, entry.scopeId), `${entry.id}.json`);
94730
+ return join27(this.scopeDir(entry.scope, entry.scopeId), `${entry.id}.json`);
93663
94731
  }
93664
94732
  // ─── Load ────────────────────────────────────────────────────────────────
93665
94733
  loadAll() {
93666
94734
  const entries2 = [];
93667
- if (!existsSync30(this.baseDir))
94735
+ if (!existsSync32(this.baseDir))
93668
94736
  return entries2;
93669
94737
  for (const scope of readdirSafe(this.baseDir)) {
93670
- const scopePath = join25(this.baseDir, scope);
94738
+ const scopePath = join27(this.baseDir, scope);
93671
94739
  for (const scopeId of readdirSafe(scopePath)) {
93672
- const idxPath = join25(scopePath, scopeId, "_index.json");
93673
- if (!existsSync30(idxPath))
94740
+ const idxPath = join27(scopePath, scopeId, "_index.json");
94741
+ if (!existsSync32(idxPath))
93674
94742
  continue;
93675
94743
  try {
93676
- const data = JSON.parse(readFileSync23(idxPath, "utf-8"));
94744
+ const data = JSON.parse(readFileSync25(idxPath, "utf-8"));
93677
94745
  entries2.push(...data);
93678
94746
  } catch (err) {
93679
- log68.warn("Failed to load index", { path: idxPath, error: String(err) });
94747
+ log70.warn("Failed to load index", { path: idxPath, error: String(err) });
93680
94748
  }
93681
94749
  }
93682
94750
  }
93683
- log68.info("Knowledge loaded from disk", { count: entries2.length });
94751
+ log70.info("Knowledge loaded from disk", { count: entries2.length });
93684
94752
  return entries2;
93685
94753
  }
93686
94754
  // ─── Write ───────────────────────────────────────────────────────────────
93687
94755
  saveEntry(entry) {
93688
94756
  const dir = this.scopeDir(entry.scope, entry.scopeId);
93689
- mkdirSync20(dir, { recursive: true });
93690
- writeFileSync18(join25(dir, `${entry.id}.json`), JSON.stringify(entry, null, 2));
94757
+ mkdirSync22(dir, { recursive: true });
94758
+ writeFileSync20(join27(dir, `${entry.id}.json`), JSON.stringify(entry, null, 2));
93691
94759
  }
93692
94760
  saveIndex(scope, scopeId, entries2) {
93693
94761
  const dir = this.scopeDir(scope, scopeId);
93694
- mkdirSync20(dir, { recursive: true });
93695
- writeFileSync18(join25(dir, "_index.json"), JSON.stringify(entries2, null, 2));
94762
+ mkdirSync22(dir, { recursive: true });
94763
+ writeFileSync20(join27(dir, "_index.json"), JSON.stringify(entries2, null, 2));
93696
94764
  }
93697
94765
  removeEntryFile(entry) {
93698
- const p = join25(this.scopeDir(entry.scope, entry.scopeId), `${entry.id}.json`);
94766
+ const p = join27(this.scopeDir(entry.scope, entry.scopeId), `${entry.id}.json`);
93699
94767
  try {
93700
94768
  unlinkSync3(p);
93701
94769
  } catch {
@@ -93718,17 +94786,17 @@ var init_file_knowledge_store = __esm({
93718
94786
  });
93719
94787
 
93720
94788
  // ../org-manager/dist/deliverable-service.js
93721
- import { existsSync as existsSync31, cpSync as cpSync3, mkdirSync as mkdirSync21 } from "node:fs";
93722
- import { join as join26, basename } from "node:path";
94789
+ import { existsSync as existsSync33, cpSync as cpSync3, mkdirSync as mkdirSync23 } from "node:fs";
94790
+ import { join as join28, basename } from "node:path";
93723
94791
  function isUrl(s2) {
93724
94792
  return /^https?:\/\//i.test(s2);
93725
94793
  }
93726
- var log69, DeliverableService;
94794
+ var log71, DeliverableService;
93727
94795
  var init_deliverable_service = __esm({
93728
94796
  "../org-manager/dist/deliverable-service.js"() {
93729
94797
  "use strict";
93730
94798
  init_dist();
93731
- log69 = createLogger("deliverable-service");
94799
+ log71 = createLogger("deliverable-service");
93732
94800
  DeliverableService = class {
93733
94801
  repo;
93734
94802
  cache = /* @__PURE__ */ new Map();
@@ -93746,7 +94814,7 @@ var init_deliverable_service = __esm({
93746
94814
  for (const r of rows) {
93747
94815
  this.cache.set(r.id, this.rowToDeliverable(r));
93748
94816
  }
93749
- log69.info("Deliverables loaded", { count: this.cache.size });
94817
+ log71.info("Deliverables loaded", { count: this.cache.size });
93750
94818
  }
93751
94819
  async create(opts) {
93752
94820
  const ref = opts.reference?.trim();
@@ -93772,7 +94840,7 @@ var init_deliverable_service = __esm({
93772
94840
  if (opts.taskId && !existing.taskId)
93773
94841
  patch.taskId = opts.taskId;
93774
94842
  const updated = await this.update(existing.id, patch);
93775
- log69.info("Deliverable upserted (updated existing)", { id: existing.id, reference: ref });
94843
+ log71.info("Deliverable upserted (updated existing)", { id: existing.id, reference: ref });
93776
94844
  this.ws?.broadcastDeliverableUpdate(existing.id, "updated", {
93777
94845
  type: opts.type,
93778
94846
  title: opts.title,
@@ -93825,7 +94893,7 @@ var init_deliverable_service = __esm({
93825
94893
  testResults: opts.testResults
93826
94894
  });
93827
94895
  this.cache.set(id, deliverable);
93828
- log69.info("Deliverable created", { id, type: opts.type, title: opts.title });
94896
+ log71.info("Deliverable created", { id, type: opts.type, title: opts.title });
93829
94897
  this.ws?.broadcastDeliverableUpdate(id, "created", {
93830
94898
  type: opts.type,
93831
94899
  title: opts.title,
@@ -93922,7 +94990,7 @@ var init_deliverable_service = __esm({
93922
94990
  if (data.testResults !== void 0 && !arrEq(data.testResults, d.testResults))
93923
94991
  changed.push("testResults");
93924
94992
  if (changed.length === 0) {
93925
- log69.debug("Deliverable update skipped (no-op)", { id });
94993
+ log71.debug("Deliverable update skipped (no-op)", { id });
93926
94994
  return d;
93927
94995
  }
93928
94996
  const now3 = (/* @__PURE__ */ new Date()).toISOString();
@@ -93956,7 +95024,7 @@ var init_deliverable_service = __esm({
93956
95024
  d.testResults = data.testResults;
93957
95025
  d.updatedAt = now3;
93958
95026
  await this.repo?.update(id, data);
93959
- log69.info("Deliverable updated", { id, fields: changed });
95027
+ log71.info("Deliverable updated", { id, fields: changed });
93960
95028
  this.ws?.broadcastDeliverableUpdate(id, "updated", {
93961
95029
  type: d.type,
93962
95030
  title: d.title,
@@ -93973,7 +95041,7 @@ var init_deliverable_service = __esm({
93973
95041
  d.status = "outdated";
93974
95042
  d.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
93975
95043
  await this.repo?.update(id, { status: "outdated" });
93976
- log69.info("Deliverable flagged outdated", { id });
95044
+ log71.info("Deliverable flagged outdated", { id });
93977
95045
  this.ws?.broadcastDeliverableUpdate(id, "removed", {
93978
95046
  type: d.type,
93979
95047
  title: d.title,
@@ -94035,7 +95103,7 @@ var init_deliverable_service = __esm({
94035
95103
  }
94036
95104
  }
94037
95105
  if (cleaned > 0) {
94038
- log69.info("Deduplicated deliverables by reference", { cleaned });
95106
+ log71.info("Deduplicated deliverables by reference", { cleaned });
94039
95107
  }
94040
95108
  return cleaned;
94041
95109
  }
@@ -94048,30 +95116,30 @@ var init_deliverable_service = __esm({
94048
95116
  const deliverables = this.findByAgent(agentId2);
94049
95117
  if (deliverables.length === 0)
94050
95118
  return 0;
94051
- const sharedDeliverables = join26(sharedDataDir, "deliverables");
95119
+ const sharedDeliverables = join28(sharedDataDir, "deliverables");
94052
95120
  let migrated = 0;
94053
95121
  for (const d of deliverables) {
94054
95122
  if (!d.reference || isUrl(d.reference))
94055
95123
  continue;
94056
95124
  if (!d.reference.startsWith(agentDir + "/") && d.reference !== agentDir)
94057
95125
  continue;
94058
- if (!existsSync31(d.reference))
95126
+ if (!existsSync33(d.reference))
94059
95127
  continue;
94060
95128
  try {
94061
- const destDir = join26(sharedDeliverables, d.id);
94062
- mkdirSync21(destDir, { recursive: true });
95129
+ const destDir = join28(sharedDeliverables, d.id);
95130
+ mkdirSync23(destDir, { recursive: true });
94063
95131
  const fileName = basename(d.reference);
94064
- const destPath = join26(destDir, fileName);
95132
+ const destPath = join28(destDir, fileName);
94065
95133
  cpSync3(d.reference, destPath, { recursive: true });
94066
95134
  await this.update(d.id, { reference: destPath });
94067
95135
  migrated++;
94068
- log69.info("Deliverable file migrated to shared", { id: d.id, from: d.reference, to: destPath });
95136
+ log71.info("Deliverable file migrated to shared", { id: d.id, from: d.reference, to: destPath });
94069
95137
  } catch (err) {
94070
- log69.warn("Failed to migrate deliverable file", { id: d.id, reference: d.reference, error: String(err) });
95138
+ log71.warn("Failed to migrate deliverable file", { id: d.id, reference: d.reference, error: String(err) });
94071
95139
  }
94072
95140
  }
94073
95141
  if (migrated > 0) {
94074
- log69.info("Migrated agent deliverable files to shared directory", { agentId: agentId2, migrated, total: deliverables.length });
95142
+ log71.info("Migrated agent deliverable files to shared directory", { agentId: agentId2, migrated, total: deliverables.length });
94075
95143
  }
94076
95144
  return migrated;
94077
95145
  }
@@ -94085,7 +95153,7 @@ var init_deliverable_service = __esm({
94085
95153
  for (const d of deliverables) {
94086
95154
  if (!d.reference || isUrl(d.reference))
94087
95155
  continue;
94088
- if (!existsSync31(d.reference)) {
95156
+ if (!existsSync33(d.reference)) {
94089
95157
  missing.push(d.id);
94090
95158
  }
94091
95159
  }
@@ -94107,7 +95175,7 @@ var init_deliverable_service = __esm({
94107
95175
  }
94108
95176
  }
94109
95177
  if (branchCleaned > 0) {
94110
- log69.info("Cleaned up legacy branch-type deliverables", { count: branchCleaned });
95178
+ log71.info("Cleaned up legacy branch-type deliverables", { count: branchCleaned });
94111
95179
  }
94112
95180
  const existingTaskIds = this.repo ? await this.repo.listTaskIdsWithDeliverables() : new Set([...this.cache.values()].map((d) => d.taskId).filter(Boolean));
94113
95181
  let migrated = 0;
@@ -94134,12 +95202,12 @@ var init_deliverable_service = __esm({
94134
95202
  });
94135
95203
  migrated++;
94136
95204
  } catch (err) {
94137
- log69.warn("Failed to migrate task deliverable", { taskId: task.id, ref: d.reference, error: String(err) });
95205
+ log71.warn("Failed to migrate task deliverable", { taskId: task.id, ref: d.reference, error: String(err) });
94138
95206
  }
94139
95207
  }
94140
95208
  }
94141
95209
  if (migrated > 0) {
94142
- log69.info("Migrated task deliverables to unified table", { migrated });
95210
+ log71.info("Migrated task deliverables to unified table", { migrated });
94143
95211
  }
94144
95212
  return migrated;
94145
95213
  }
@@ -94194,12 +95262,12 @@ var init_deliverable_service = __esm({
94194
95262
  });
94195
95263
 
94196
95264
  // ../org-manager/dist/report-service.js
94197
- var log70, ReportService;
95265
+ var log72, ReportService;
94198
95266
  var init_report_service = __esm({
94199
95267
  "../org-manager/dist/report-service.js"() {
94200
95268
  "use strict";
94201
95269
  init_dist();
94202
- log70 = createLogger("report-service");
95270
+ log72 = createLogger("report-service");
94203
95271
  ReportService = class {
94204
95272
  taskService;
94205
95273
  billingService;
@@ -94252,7 +95320,7 @@ var init_report_service = __esm({
94252
95320
  generatedBy: opts.generatedBy ?? "system"
94253
95321
  };
94254
95322
  this.reports.set(report.id, report);
94255
- log70.info("Report generated", { id: report.id, type: report.type, scope: report.scope });
95323
+ log72.info("Report generated", { id: report.id, type: report.type, scope: report.scope });
94256
95324
  return report;
94257
95325
  }
94258
95326
  getReport(id) {
@@ -94274,7 +95342,7 @@ var init_report_service = __esm({
94274
95342
  if (!report?.upcomingPlan)
94275
95343
  throw new Error("Report has no plan");
94276
95344
  report.upcomingPlan.status = "pending";
94277
- log70.info("Plan submitted for approval", { reportId });
95345
+ log72.info("Plan submitted for approval", { reportId });
94278
95346
  }
94279
95347
  approvePlan(reportId, userId2) {
94280
95348
  const report = this.reports.get(reportId);
@@ -94301,7 +95369,7 @@ var init_report_service = __esm({
94301
95369
  projectId: report.scope === "project" ? report.scopeId : void 0
94302
95370
  });
94303
95371
  }
94304
- log70.info("Plan approved \u2014 tasks created", {
95372
+ log72.info("Plan approved \u2014 tasks created", {
94305
95373
  reportId,
94306
95374
  taskCount: report.upcomingPlan.plannedTasks.length
94307
95375
  });
@@ -94313,7 +95381,7 @@ var init_report_service = __esm({
94313
95381
  throw new Error("Report has no plan");
94314
95382
  report.upcomingPlan.status = "rejected";
94315
95383
  report.upcomingPlan.rejectionReason = reason;
94316
- log70.info("Plan rejected", { reportId, reason });
95384
+ log72.info("Plan rejected", { reportId, reason });
94317
95385
  return report;
94318
95386
  }
94319
95387
  // ─── Feedback ──────────────────────────────────────────────────────────────
@@ -94365,7 +95433,7 @@ var init_report_service = __esm({
94365
95433
  const existing = this.feedbackStore.get(opts.reportId) ?? [];
94366
95434
  existing.push(feedback);
94367
95435
  this.feedbackStore.set(opts.reportId, existing);
94368
- log70.info("Feedback added to report", {
95436
+ log72.info("Feedback added to report", {
94369
95437
  reportId: opts.reportId,
94370
95438
  type: opts.type,
94371
95439
  disclosure: opts.disclosure.scope
@@ -94450,12 +95518,12 @@ var init_report_service = __esm({
94450
95518
  });
94451
95519
 
94452
95520
  // ../org-manager/dist/trust-service.js
94453
- var log71, TrustService;
95521
+ var log73, TrustService;
94454
95522
  var init_trust_service = __esm({
94455
95523
  "../org-manager/dist/trust-service.js"() {
94456
95524
  "use strict";
94457
95525
  init_dist();
94458
- log71 = createLogger("trust-service");
95526
+ log73 = createLogger("trust-service");
94459
95527
  TrustService = class {
94460
95528
  trustLevels = /* @__PURE__ */ new Map();
94461
95529
  getOrCreate(agentId2) {
@@ -94504,7 +95572,7 @@ var init_trust_service = __esm({
94504
95572
  trust.level = this.scoreToLevel(trust.score, trust.totalDeliveries);
94505
95573
  trust.lastEvaluatedAt = (/* @__PURE__ */ new Date()).toISOString();
94506
95574
  if (trust.level !== oldLevel) {
94507
- log71.info("Trust level changed", {
95575
+ log73.info("Trust level changed", {
94508
95576
  agentId: trust.agentId,
94509
95577
  oldLevel,
94510
95578
  newLevel: trust.level,
@@ -94549,12 +95617,12 @@ var init_trust_service = __esm({
94549
95617
  });
94550
95618
 
94551
95619
  // ../org-manager/dist/archive-service.js
94552
- var log72, ARCHIVABLE_STATUSES, ACTIVE_DISCUSSION_DAYS, ACTIVE_DISCUSSION_MS, ArchiveService;
95620
+ var log74, ARCHIVABLE_STATUSES, ACTIVE_DISCUSSION_DAYS, ACTIVE_DISCUSSION_MS, ArchiveService;
94553
95621
  var init_archive_service = __esm({
94554
95622
  "../org-manager/dist/archive-service.js"() {
94555
95623
  "use strict";
94556
95624
  init_dist();
94557
- log72 = createLogger("archive-service");
95625
+ log74 = createLogger("archive-service");
94558
95626
  ARCHIVABLE_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "rejected", "cancelled"]);
94559
95627
  ACTIVE_DISCUSSION_DAYS = 7;
94560
95628
  ACTIVE_DISCUSSION_MS = ACTIVE_DISCUSSION_DAYS * 864e5;
@@ -94575,11 +95643,11 @@ var init_archive_service = __esm({
94575
95643
  * then repeats at the configured interval.
94576
95644
  */
94577
95645
  start(intervalMs = ARCHIVE_SCAN_INTERVAL_MS) {
94578
- this.runArchiveScan().catch((err) => log72.warn("Initial archive scan failed", { error: String(err) }));
95646
+ this.runArchiveScan().catch((err) => log74.warn("Initial archive scan failed", { error: String(err) }));
94579
95647
  this.scanInterval = setInterval(() => {
94580
- this.runArchiveScan().catch((err) => log72.warn("Archive scan failed", { error: String(err) }));
95648
+ this.runArchiveScan().catch((err) => log74.warn("Archive scan failed", { error: String(err) }));
94581
95649
  }, intervalMs);
94582
- log72.info("Archive service started", { intervalMs });
95650
+ log74.info("Archive service started", { intervalMs });
94583
95651
  }
94584
95652
  stop() {
94585
95653
  if (this.scanInterval) {
@@ -94591,7 +95659,7 @@ var init_archive_service = __esm({
94591
95659
  const archivedTasks = await this.archiveTasks();
94592
95660
  const archivedRequirements = this.archiveRequirements();
94593
95661
  if (archivedTasks > 0 || archivedRequirements > 0) {
94594
- log72.info("Archive scan complete", { archivedTasks, archivedRequirements });
95662
+ log74.info("Archive scan complete", { archivedTasks, archivedRequirements });
94595
95663
  }
94596
95664
  return { archivedTasks, archivedRequirements };
94597
95665
  }
@@ -94614,7 +95682,7 @@ var init_archive_service = __esm({
94614
95682
  this.taskService.archiveTask(task.id);
94615
95683
  archived++;
94616
95684
  } catch (err) {
94617
- log72.warn("Failed to archive task", { taskId: task.id, error: String(err) });
95685
+ log74.warn("Failed to archive task", { taskId: task.id, error: String(err) });
94618
95686
  }
94619
95687
  }
94620
95688
  }
@@ -94639,7 +95707,7 @@ var init_archive_service = __esm({
94639
95707
  this.requirementService.updateRequirementStatus(req.id, "archived");
94640
95708
  archived++;
94641
95709
  } catch (err) {
94642
- log72.warn("Failed to archive requirement", { requirementId: req.id, error: String(err) });
95710
+ log74.warn("Failed to archive requirement", { requirementId: req.id, error: String(err) });
94643
95711
  }
94644
95712
  }
94645
95713
  }
@@ -94681,12 +95749,12 @@ var init_archive_service = __esm({
94681
95749
  });
94682
95750
 
94683
95751
  // ../org-manager/dist/stale-detector.js
94684
- var log73, DEFAULT_CONFIG4, StaleDetector;
95752
+ var log75, DEFAULT_CONFIG4, StaleDetector;
94685
95753
  var init_stale_detector = __esm({
94686
95754
  "../org-manager/dist/stale-detector.js"() {
94687
95755
  "use strict";
94688
95756
  init_dist();
94689
- log73 = createLogger("stale-detector");
95757
+ log75 = createLogger("stale-detector");
94690
95758
  DEFAULT_CONFIG4 = {
94691
95759
  maxInProgressMs: 24 * 60 * 60 * 1e3,
94692
95760
  maxReviewWaitMs: 12 * 60 * 60 * 1e3,
@@ -94709,9 +95777,9 @@ var init_stale_detector = __esm({
94709
95777
  if (items.length > 0 && this.onStaleItems) {
94710
95778
  this.onStaleItems(items);
94711
95779
  }
94712
- }).catch((err) => log73.warn("Stale scan failed", { error: String(err) }));
95780
+ }).catch((err) => log75.warn("Stale scan failed", { error: String(err) }));
94713
95781
  }, intervalMs);
94714
- log73.info("Stale detector started", { intervalMs });
95782
+ log75.info("Stale detector started", { intervalMs });
94715
95783
  }
94716
95784
  stop() {
94717
95785
  if (this.scanInterval) {
@@ -94754,7 +95822,7 @@ var init_stale_detector = __esm({
94754
95822
  }
94755
95823
  }
94756
95824
  if (staleItems.length > 0) {
94757
- log73.info(`Found ${staleItems.length} stale items`);
95825
+ log75.info(`Found ${staleItems.length} stale items`);
94758
95826
  }
94759
95827
  return staleItems;
94760
95828
  }
@@ -94763,12 +95831,12 @@ var init_stale_detector = __esm({
94763
95831
  });
94764
95832
 
94765
95833
  // ../org-manager/dist/scheduled-task-runner.js
94766
- var log74, MIN_STAGGER_MS, MAX_STAGGER_MS, ScheduledTaskRunner;
95834
+ var log76, MIN_STAGGER_MS, MAX_STAGGER_MS, ScheduledTaskRunner;
94767
95835
  var init_scheduled_task_runner = __esm({
94768
95836
  "../org-manager/dist/scheduled-task-runner.js"() {
94769
95837
  "use strict";
94770
95838
  init_dist();
94771
- log74 = createLogger("scheduled-task-runner");
95839
+ log76 = createLogger("scheduled-task-runner");
94772
95840
  MIN_STAGGER_MS = 2 * 6e4;
94773
95841
  MAX_STAGGER_MS = 15 * 6e4;
94774
95842
  ScheduledTaskRunner = class {
@@ -94787,11 +95855,11 @@ var init_scheduled_task_runner = __esm({
94787
95855
  return;
94788
95856
  this.running = true;
94789
95857
  this.startedAt = Date.now();
94790
- this.tick().catch((e) => log74.error("Initial scheduled task tick failed", { error: String(e) }));
95858
+ this.tick().catch((e) => log76.error("Initial scheduled task tick failed", { error: String(e) }));
94791
95859
  this.timer = setInterval(() => {
94792
- this.tick().catch((e) => log74.error("Scheduled task tick failed", { error: String(e) }));
95860
+ this.tick().catch((e) => log76.error("Scheduled task tick failed", { error: String(e) }));
94793
95861
  }, this.pollIntervalMs);
94794
- log74.info("ScheduledTaskRunner started", { pollIntervalMs: this.pollIntervalMs });
95862
+ log76.info("ScheduledTaskRunner started", { pollIntervalMs: this.pollIntervalMs });
94795
95863
  }
94796
95864
  stop() {
94797
95865
  if (this.timer) {
@@ -94802,7 +95870,7 @@ var init_scheduled_task_runner = __esm({
94802
95870
  clearTimeout(t);
94803
95871
  this.staggerTimers = [];
94804
95872
  this.running = false;
94805
- log74.info("ScheduledTaskRunner stopped");
95873
+ log76.info("ScheduledTaskRunner stopped");
94806
95874
  }
94807
95875
  isRunning() {
94808
95876
  return this.running;
@@ -94836,7 +95904,7 @@ var init_scheduled_task_runner = __esm({
94836
95904
  }
94837
95905
  if (startup && nextRun < this.startedAt) {
94838
95906
  const delay = MIN_STAGGER_MS + Math.random() * (MAX_STAGGER_MS - MIN_STAGGER_MS);
94839
- log74.info("Staggering overdue scheduled task", {
95907
+ log76.info("Staggering overdue scheduled task", {
94840
95908
  taskId: task.id,
94841
95909
  title: task.title,
94842
95910
  overdueBy: `${Math.round((now3 - nextRun) / 6e4)}m`,
@@ -94845,7 +95913,7 @@ var init_scheduled_task_runner = __esm({
94845
95913
  const timer = setTimeout(() => {
94846
95914
  if (!this.running)
94847
95915
  return;
94848
- this.fireScheduledTask(task).catch((e) => log74.error("Failed to fire staggered scheduled task", { taskId: task.id, error: String(e) }));
95916
+ this.fireScheduledTask(task).catch((e) => log76.error("Failed to fire staggered scheduled task", { taskId: task.id, error: String(e) }));
94849
95917
  }, delay);
94850
95918
  this.staggerTimers.push(timer);
94851
95919
  continue;
@@ -94853,27 +95921,27 @@ var init_scheduled_task_runner = __esm({
94853
95921
  try {
94854
95922
  await this.fireScheduledTask(task);
94855
95923
  } catch (e) {
94856
- log74.error("Failed to fire scheduled task", { taskId: task.id, error: String(e) });
95924
+ log76.error("Failed to fire scheduled task", { taskId: task.id, error: String(e) });
94857
95925
  }
94858
95926
  }
94859
95927
  }
94860
95928
  async fireScheduledTask(task) {
94861
- log74.info("Firing scheduled task", { taskId: task.id, title: task.title });
95929
+ log76.info("Firing scheduled task", { taskId: task.id, title: task.title });
94862
95930
  await this.taskService.advanceScheduleConfig(task.id);
94863
95931
  const resettableStatuses = ["completed", "cancelled", "failed"];
94864
95932
  if (resettableStatuses.includes(task.status)) {
94865
95933
  await this.taskService.resetTaskForRerun(task.id);
94866
95934
  } else if (!["in_progress", "review", "blocked", "pending"].includes(task.status)) {
94867
- log74.warn("Scheduled task has unexpected status, resetting for rerun", { taskId: task.id, status: task.status });
95935
+ log76.warn("Scheduled task has unexpected status, resetting for rerun", { taskId: task.id, status: task.status });
94868
95936
  await this.taskService.resetTaskForRerun(task.id);
94869
95937
  }
94870
95938
  const current = this.taskService.getTask(task.id);
94871
95939
  if (current && current.status === "in_progress") {
94872
95940
  try {
94873
95941
  await this.taskService.runTask(task.id);
94874
- log74.info("Scheduled task auto-started", { taskId: task.id });
95942
+ log76.info("Scheduled task auto-started", { taskId: task.id });
94875
95943
  } catch (err) {
94876
- log74.warn("Failed to auto-start scheduled task (agent may be busy)", { taskId: task.id, error: String(err) });
95944
+ log76.warn("Failed to auto-start scheduled task (agent may be busy)", { taskId: task.id, error: String(err) });
94877
95945
  }
94878
95946
  }
94879
95947
  }
@@ -94883,11 +95951,11 @@ var init_scheduled_task_runner = __esm({
94883
95951
 
94884
95952
  // ../storage/dist/sqlite-storage.js
94885
95953
  import { DatabaseSync } from "node:sqlite";
94886
- import { randomUUID } from "node:crypto";
94887
- import { mkdirSync as mkdirSync22 } from "node:fs";
94888
- import { dirname as dirname9 } from "node:path";
95954
+ import { randomUUID as randomUUID2 } from "node:crypto";
95955
+ import { mkdirSync as mkdirSync24 } from "node:fs";
95956
+ import { dirname as dirname11 } from "node:path";
94889
95957
  function generateId2(prefix = "") {
94890
- const uuid = randomUUID().replace(/-/g, "").slice(0, 16);
95958
+ const uuid = randomUUID2().replace(/-/g, "").slice(0, 16);
94891
95959
  return prefix ? `${prefix}_${uuid}` : uuid;
94892
95960
  }
94893
95961
  function now2() {
@@ -94905,7 +95973,7 @@ function toDate(v) {
94905
95973
  function openSqlite(dbPath) {
94906
95974
  if (_db)
94907
95975
  return _db;
94908
- mkdirSync22(dirname9(dbPath), { recursive: true });
95976
+ mkdirSync24(dirname11(dbPath), { recursive: true });
94909
95977
  _db = new DatabaseSync(dbPath);
94910
95978
  _db.exec("PRAGMA journal_mode = WAL");
94911
95979
  _db.exec("PRAGMA foreign_keys = ON");
@@ -94949,6 +96017,8 @@ function openSqlite(dbPath) {
94949
96017
  { table: "projects", column: "created_by", sql: "ALTER TABLE projects ADD COLUMN created_by TEXT" },
94950
96018
  { table: "approvals", column: "target_user_id", sql: "ALTER TABLE approvals ADD COLUMN target_user_id TEXT" },
94951
96019
  { table: "users", column: "deleted_at", sql: "ALTER TABLE users ADD COLUMN deleted_at TEXT" },
96020
+ { table: "users", column: "hub_user_id", sql: "ALTER TABLE users ADD COLUMN hub_user_id TEXT" },
96021
+ { table: "users", column: "hub_username", sql: "ALTER TABLE users ADD COLUMN hub_username TEXT" },
94952
96022
  { table: "agents", column: "deleted_at", sql: "ALTER TABLE agents ADD COLUMN deleted_at TEXT" },
94953
96023
  { table: "deliverables", column: "format", sql: "ALTER TABLE deliverables ADD COLUMN format TEXT" },
94954
96024
  { table: "task_comments", column: "reply_to_id", sql: "ALTER TABLE task_comments ADD COLUMN reply_to_id TEXT" },
@@ -94958,7 +96028,7 @@ function openSqlite(dbPath) {
94958
96028
  const cols = _db.prepare(`PRAGMA table_info(${m.table})`).all();
94959
96029
  if (!cols.some((c) => c.name === m.column)) {
94960
96030
  _db.exec(m.sql);
94961
- log75.info(`Migration: added column ${m.column} to ${m.table}`);
96031
+ log77.info(`Migration: added column ${m.column} to ${m.table}`);
94962
96032
  }
94963
96033
  }
94964
96034
  _db.exec("CREATE INDEX IF NOT EXISTS idx_agent_activities_mailbox ON agent_activities(mailbox_item_id)");
@@ -94972,10 +96042,10 @@ function openSqlite(dbPath) {
94972
96042
  for (const m of statusMigrations) {
94973
96043
  const result = _db.prepare(m.sql).run();
94974
96044
  if (result.changes > 0) {
94975
- log75.info(`Status migration: ${m.desc} (${result.changes} rows)`);
96045
+ log77.info(`Status migration: ${m.desc} (${result.changes} rows)`);
94976
96046
  }
94977
96047
  }
94978
- log75.info("SQLite database opened", { path: dbPath });
96048
+ log77.info("SQLite database opened", { path: dbPath });
94979
96049
  return _db;
94980
96050
  }
94981
96051
  function closeSqlite() {
@@ -94995,7 +96065,7 @@ function migrateToExecutionStreamLogs(db) {
94995
96065
  SELECT id, 'task', task_id, agent_id, seq, type, content, metadata, execution_round, created_at
94996
96066
  FROM task_logs
94997
96067
  `);
94998
- log75.info(`Migration: copied ${taskLogCount} task_logs to execution_stream_logs`);
96068
+ log77.info(`Migration: copied ${taskLogCount} task_logs to execution_stream_logs`);
94999
96069
  }
95000
96070
  const actLogCount = db.prepare("SELECT COUNT(*) as cnt FROM agent_activity_logs").get().cnt;
95001
96071
  if (actLogCount > 0) {
@@ -95006,15 +96076,15 @@ function migrateToExecutionStreamLogs(db) {
95006
96076
  seq, type, content, metadata, NULL, created_at
95007
96077
  FROM agent_activity_logs
95008
96078
  `);
95009
- log75.info(`Migration: copied ${actLogCount} agent_activity_logs to execution_stream_logs`);
96079
+ log77.info(`Migration: copied ${actLogCount} agent_activity_logs to execution_stream_logs`);
95010
96080
  }
95011
96081
  }
95012
- var log75, SCHEMA_SQL, _db, SqliteOrgRepo, SqliteAgentRepo, SqliteTaskRepo, SqliteRequirementRepo, SqliteProjectRepo, SqliteAuditRepo, SqliteTaskLogRepo, SqliteTaskCommentRepo, SqliteRequirementCommentRepo, SqliteMessageRepo, SqliteChatSessionRepo, SqliteChannelMessageRepo, SqliteUserRepo, SqliteTeamRepo, SqliteMarketplaceTemplateRepo, SqliteMarketplaceSkillRepo, SqliteMarketplaceRatingRepo, SqliteAgentKnowledgeRepo, SqliteExternalAgentRepo, SqliteDeliverableRepo, SqliteActivityRepo, SqliteExecutionStreamRepo, SqliteMailboxRepo, SqliteDecisionRepo, SqliteNotificationRepo, SqliteApprovalRepo, SqliteGroupChatRepo, SqliteStatusTransitionRepo, SqliteReadCursorRepo;
96082
+ var log77, SCHEMA_SQL, _db, SqliteOrgRepo, SqliteAgentRepo, SqliteTaskRepo, SqliteRequirementRepo, SqliteProjectRepo, SqliteAuditRepo, SqliteTaskLogRepo, SqliteTaskCommentRepo, SqliteRequirementCommentRepo, SqliteMessageRepo, SqliteChatSessionRepo, SqliteChannelMessageRepo, SqliteUserRepo, SqliteTeamRepo, SqliteMarketplaceTemplateRepo, SqliteMarketplaceSkillRepo, SqliteMarketplaceRatingRepo, SqliteAgentKnowledgeRepo, SqliteExternalAgentRepo, SqliteDeliverableRepo, SqliteActivityRepo, SqliteExecutionStreamRepo, SqliteMailboxRepo, SqliteDecisionRepo, SqliteNotificationRepo, SqliteApprovalRepo, SqliteGroupChatRepo, SqliteStatusTransitionRepo, SqliteReadCursorRepo;
95013
96083
  var init_sqlite_storage = __esm({
95014
96084
  "../storage/dist/sqlite-storage.js"() {
95015
96085
  "use strict";
95016
96086
  init_dist();
95017
- log75 = createLogger("sqlite-storage");
96087
+ log77 = createLogger("sqlite-storage");
95018
96088
  SCHEMA_SQL = `
95019
96089
  CREATE TABLE IF NOT EXISTS organizations (
95020
96090
  id TEXT PRIMARY KEY,
@@ -96496,7 +97566,7 @@ CREATE TABLE IF NOT EXISTS user_read_cursors (
96496
97566
  migrated++;
96497
97567
  }
96498
97568
  if (migrated > 0) {
96499
- log75.info(`Migrated ${migrated} legacy chat messages to segment format`);
97569
+ log77.info(`Migrated ${migrated} legacy chat messages to segment format`);
96500
97570
  }
96501
97571
  return migrated;
96502
97572
  }
@@ -96508,7 +97578,7 @@ CREATE TABLE IF NOT EXISTS user_read_cursors (
96508
97578
  const result = this.db.prepare("UPDATE chat_sessions SET user_id = ? WHERE user_id IS NULL").run(defaultUserId);
96509
97579
  const count = Number(result.changes);
96510
97580
  if (count > 0) {
96511
- log75.info(`Migrated ${count} chat sessions with NULL user_id to user ${defaultUserId}`);
97581
+ log77.info(`Migrated ${count} chat sessions with NULL user_id to user ${defaultUserId}`);
96512
97582
  }
96513
97583
  return count;
96514
97584
  }
@@ -96522,7 +97592,7 @@ CREATE TABLE IF NOT EXISTS user_read_cursors (
96522
97592
  const result = this.db.prepare("UPDATE chat_sessions SET user_id = ? WHERE user_id = 'default'").run(realOwnerId);
96523
97593
  const count = Number(result.changes);
96524
97594
  if (count > 0) {
96525
- log75.info(`Migrated ${count} chat sessions from user_id='default' to ${realOwnerId}`);
97595
+ log77.info(`Migrated ${count} chat sessions from user_id='default' to ${realOwnerId}`);
96526
97596
  }
96527
97597
  return count;
96528
97598
  }
@@ -96668,13 +97738,13 @@ CREATE TABLE IF NOT EXISTS user_read_cursors (
96668
97738
  this.db = db;
96669
97739
  }
96670
97740
  create(data) {
96671
- this.db.prepare("INSERT INTO users (id, org_id, name, email, role, team_id, password_hash, created_at) VALUES (?,?,?,?,?,?,?,?)").run(data.id, data.orgId, data.name, data.email ?? null, data.role ?? "member", data.teamId ?? null, data.passwordHash ?? null, now2());
97741
+ this.db.prepare("INSERT INTO users (id, org_id, name, email, role, team_id, password_hash, hub_user_id, avatar_url, created_at) VALUES (?,?,?,?,?,?,?,?,?,?)").run(data.id, data.orgId, data.name, data.email ?? null, data.role ?? "member", data.teamId ?? null, data.passwordHash ?? null, data.hubUserId ?? null, data.avatarUrl ?? null, now2());
96672
97742
  return this.findById(data.id);
96673
97743
  }
96674
97744
  async upsert(data) {
96675
- this.db.prepare(`INSERT INTO users (id, org_id, name, email, role, team_id, password_hash, created_at)
96676
- VALUES (?,?,?,?,?,?,?,?)
96677
- ON CONFLICT(id) DO UPDATE SET name = excluded.name, email = excluded.email, role = excluded.role, team_id = excluded.team_id, password_hash = COALESCE(excluded.password_hash, password_hash)`).run(data.id, data.orgId, data.name, data.email ?? null, data.role ?? "member", data.teamId ?? null, data.passwordHash ?? null, now2());
97745
+ this.db.prepare(`INSERT INTO users (id, org_id, name, email, role, team_id, password_hash, hub_user_id, created_at)
97746
+ VALUES (?,?,?,?,?,?,?,?,?)
97747
+ ON CONFLICT(id) DO UPDATE SET name = excluded.name, email = excluded.email, role = excluded.role, team_id = excluded.team_id, password_hash = COALESCE(excluded.password_hash, password_hash), hub_user_id = COALESCE(excluded.hub_user_id, hub_user_id)`).run(data.id, data.orgId, data.name, data.email ?? null, data.role ?? "member", data.teamId ?? null, data.passwordHash ?? null, data.hubUserId ?? null, now2());
96678
97748
  }
96679
97749
  async updateTeamId(id, teamId) {
96680
97750
  this.db.prepare("UPDATE users SET team_id = ? WHERE id = ?").run(teamId, id);
@@ -96689,6 +97759,17 @@ CREATE TABLE IF NOT EXISTS user_read_cursors (
96689
97759
  const r = this.db.prepare("SELECT * FROM users WHERE email = ? AND deleted_at IS NULL").get(email);
96690
97760
  return r ? this._map(r) : null;
96691
97761
  }
97762
+ findByHubUserId(hubUserId) {
97763
+ const r = this.db.prepare("SELECT * FROM users WHERE hub_user_id = ? AND deleted_at IS NULL").get(hubUserId);
97764
+ return r ? this._map(r) : null;
97765
+ }
97766
+ updateHubUserId(id, hubUserId, hubUsername) {
97767
+ if (hubUsername) {
97768
+ this.db.prepare("UPDATE users SET hub_user_id = ?, hub_username = ? WHERE id = ?").run(hubUserId, hubUsername, id);
97769
+ } else {
97770
+ this.db.prepare("UPDATE users SET hub_user_id = ? WHERE id = ?").run(hubUserId, id);
97771
+ }
97772
+ }
96692
97773
  findById(id) {
96693
97774
  const r = this.db.prepare("SELECT * FROM users WHERE id = ?").get(id);
96694
97775
  return r ? this._map(r) : null;
@@ -96765,7 +97846,7 @@ CREATE TABLE IF NOT EXISTS user_read_cursors (
96765
97846
  return newId;
96766
97847
  }
96767
97848
  this.db.prepare("UPDATE users SET id = ? WHERE id = 'default'").run(newId);
96768
- log75.info(`Migrated user id='default' to '${newId}'`);
97849
+ log77.info(`Migrated user id='default' to '${newId}'`);
96769
97850
  return newId;
96770
97851
  }
96771
97852
  _map(r) {
@@ -96778,6 +97859,8 @@ CREATE TABLE IF NOT EXISTS user_read_cursors (
96778
97859
  teamId: r["team_id"],
96779
97860
  passwordHash: r["password_hash"],
96780
97861
  avatarUrl: r["avatar_url"],
97862
+ hubUserId: r["hub_user_id"],
97863
+ hubUsername: r["hub_username"],
96781
97864
  inviteToken: r["invite_token"],
96782
97865
  inviteExpiresAt: r["invite_expires_at"],
96783
97866
  createdAt: toDate(r["created_at"]),
@@ -97824,7 +98907,7 @@ CREATE TABLE IF NOT EXISTS user_read_cursors (
97824
98907
  const result = this.db.prepare("UPDATE user_notifications SET user_id = ? WHERE user_id = 'default'").run(realOwnerId);
97825
98908
  const count = Number(result.changes);
97826
98909
  if (count > 0) {
97827
- log75.info(`Migrated ${count} notifications from user_id='default' to ${realOwnerId}`);
98910
+ log77.info(`Migrated ${count} notifications from user_id='default' to ${realOwnerId}`);
97828
98911
  }
97829
98912
  return count;
97830
98913
  }
@@ -97905,7 +98988,7 @@ CREATE TABLE IF NOT EXISTS user_read_cursors (
97905
98988
  const result = this.db.prepare("UPDATE approvals SET target_user_id = ? WHERE target_user_id = 'default'").run(realOwnerId);
97906
98989
  const count = Number(result.changes);
97907
98990
  if (count > 0) {
97908
- log75.info(`Migrated ${count} approvals from target_user_id='default' to ${realOwnerId}`);
98991
+ log77.info(`Migrated ${count} approvals from target_user_id='default' to ${realOwnerId}`);
97909
98992
  }
97910
98993
  return count;
97911
98994
  }
@@ -98154,17 +99237,17 @@ var init_dist5 = __esm({
98154
99237
  });
98155
99238
 
98156
99239
  // ../org-manager/dist/storage-bridge.js
98157
- import { homedir as homedir16 } from "node:os";
98158
- import { join as join27 } from "node:path";
99240
+ import { homedir as homedir18 } from "node:os";
99241
+ import { join as join29 } from "node:path";
98159
99242
  function resolveSqlitePath(url) {
98160
99243
  if (url?.startsWith("sqlite:")) {
98161
99244
  let p = url.slice("sqlite:".length);
98162
99245
  if (p.startsWith("~/") || p === "~") {
98163
- p = join27(homedir16(), p.slice(2));
99246
+ p = join29(homedir18(), p.slice(2));
98164
99247
  }
98165
99248
  return p;
98166
99249
  }
98167
- return join27(homedir16(), ".markus", "data.db");
99250
+ return join29(homedir18(), ".markus", "data.db");
98168
99251
  }
98169
99252
  async function initStorage(databaseUrl) {
98170
99253
  const url = databaseUrl ?? process.env["DATABASE_URL"];
@@ -98202,28 +99285,28 @@ async function initSqliteStorage(url) {
98202
99285
  statusTransitionRepo: new storage.SqliteStatusTransitionRepo(db),
98203
99286
  readCursorRepo: new storage.SqliteReadCursorRepo(db)
98204
99287
  };
98205
- log76.info("SQLite storage initialized", { path: dbPath });
99288
+ log78.info("SQLite storage initialized", { path: dbPath });
98206
99289
  return bridge;
98207
99290
  } catch (error) {
98208
- log76.warn("Failed to initialize SQLite storage, falling back to memory-only mode", {
99291
+ log78.warn("Failed to initialize SQLite storage, falling back to memory-only mode", {
98209
99292
  error: String(error)
98210
99293
  });
98211
99294
  return null;
98212
99295
  }
98213
99296
  }
98214
- var log76;
99297
+ var log78;
98215
99298
  var init_storage_bridge = __esm({
98216
99299
  "../org-manager/dist/storage-bridge.js"() {
98217
99300
  "use strict";
98218
99301
  init_dist();
98219
- log76 = createLogger("storage-bridge");
99302
+ log78 = createLogger("storage-bridge");
98220
99303
  }
98221
99304
  });
98222
99305
 
98223
99306
  // ../org-manager/dist/file-storage-provider.js
98224
- import { mkdirSync as mkdirSync23, writeFileSync as writeFileSync19, unlinkSync as unlinkSync4, existsSync as existsSync32 } from "node:fs";
98225
- import { join as join28, extname } from "node:path";
98226
- import { homedir as homedir17 } from "node:os";
99307
+ import { mkdirSync as mkdirSync25, writeFileSync as writeFileSync21, unlinkSync as unlinkSync4, existsSync as existsSync34 } from "node:fs";
99308
+ import { join as join30, extname } from "node:path";
99309
+ import { homedir as homedir19 } from "node:os";
98227
99310
  function mimeToExt(mime) {
98228
99311
  const map = {
98229
99312
  "image/jpeg": ".jpg",
@@ -98243,27 +99326,27 @@ var init_file_storage_provider = __esm({
98243
99326
  LocalFileStorageProvider = class {
98244
99327
  baseDir;
98245
99328
  constructor(baseDir) {
98246
- this.baseDir = baseDir ?? join28(homedir17(), ".markus", "uploads");
98247
- mkdirSync23(this.baseDir, { recursive: true });
99329
+ this.baseDir = baseDir ?? join30(homedir19(), ".markus", "uploads");
99330
+ mkdirSync25(this.baseDir, { recursive: true });
98248
99331
  }
98249
99332
  async upload(data, opts) {
98250
99333
  const ext = extname(opts.name) || mimeToExt(opts.contentType);
98251
99334
  const key2 = `${generateId("upl")}${ext}`;
98252
- const subDir = opts.prefix ? join28(this.baseDir, opts.prefix) : this.baseDir;
98253
- mkdirSync23(subDir, { recursive: true });
98254
- writeFileSync19(join28(subDir, key2), data);
99335
+ const subDir = opts.prefix ? join30(this.baseDir, opts.prefix) : this.baseDir;
99336
+ mkdirSync25(subDir, { recursive: true });
99337
+ writeFileSync21(join30(subDir, key2), data);
98255
99338
  const urlPath = opts.prefix ? `/api/uploads/${opts.prefix}/${key2}` : `/api/uploads/${key2}`;
98256
99339
  return { url: urlPath, key: opts.prefix ? `${opts.prefix}/${key2}` : key2 };
98257
99340
  }
98258
99341
  async delete(key2) {
98259
- const filePath = join28(this.baseDir, key2);
98260
- if (existsSync32(filePath)) {
99342
+ const filePath = join30(this.baseDir, key2);
99343
+ if (existsSync34(filePath)) {
98261
99344
  unlinkSync4(filePath);
98262
99345
  }
98263
99346
  }
98264
99347
  /** Resolve a storage key to an absolute filesystem path (for serving). */
98265
99348
  resolve(key2) {
98266
- return join28(this.baseDir, key2);
99349
+ return join30(this.baseDir, key2);
98267
99350
  }
98268
99351
  };
98269
99352
  }
@@ -98281,6 +99364,7 @@ __export(dist_exports5, {
98281
99364
  FileKnowledgeStore: () => FileKnowledgeStore,
98282
99365
  HITLService: () => HITLService,
98283
99366
  KnowledgeService: () => KnowledgeService,
99367
+ LicenseService: () => LicenseService,
98284
99368
  LocalFileStorageProvider: () => LocalFileStorageProvider,
98285
99369
  OrganizationService: () => OrganizationService,
98286
99370
  ProjectService: () => ProjectService,
@@ -98289,6 +99373,7 @@ __export(dist_exports5, {
98289
99373
  ScheduledTaskRunner: () => ScheduledTaskRunner,
98290
99374
  StaleDetector: () => StaleDetector,
98291
99375
  TaskService: () => TaskService,
99376
+ TelemetryService: () => TelemetryService,
98292
99377
  TrustService: () => TrustService,
98293
99378
  WSBroadcaster: () => WSBroadcaster,
98294
99379
  initStorage: () => initStorage,
@@ -98304,6 +99389,8 @@ var init_dist6 = __esm({
98304
99389
  init_ws_server();
98305
99390
  init_hitl_service();
98306
99391
  init_billing_service();
99392
+ init_license_service();
99393
+ init_telemetry_service();
98307
99394
  init_audit_service();
98308
99395
  init_project_service();
98309
99396
  init_requirement_service();
@@ -98323,12 +99410,12 @@ var init_dist6 = __esm({
98323
99410
  });
98324
99411
 
98325
99412
  // ../comms/dist/feishu/client.js
98326
- var log77, FeishuClient;
99413
+ var log79, FeishuClient;
98327
99414
  var init_client = __esm({
98328
99415
  "../comms/dist/feishu/client.js"() {
98329
99416
  "use strict";
98330
99417
  init_dist();
98331
- log77 = createLogger("feishu-client");
99418
+ log79 = createLogger("feishu-client");
98332
99419
  FeishuClient = class {
98333
99420
  appId;
98334
99421
  appSecret;
@@ -98358,7 +99445,7 @@ var init_client = __esm({
98358
99445
  }
98359
99446
  this.tenantToken = data.tenant_access_token;
98360
99447
  this.tokenExpiresAt = Date.now() + (data.expire - 300) * 1e3;
98361
- log77.info("Feishu tenant token refreshed");
99448
+ log79.info("Feishu tenant token refreshed");
98362
99449
  return this.tenantToken;
98363
99450
  }
98364
99451
  async sendTextMessage(chatId, text) {
@@ -98492,13 +99579,13 @@ var init_client = __esm({
98492
99579
  import { createServer as createServer3 } from "node:http";
98493
99580
  import { createDecipheriv, scrypt } from "node:crypto";
98494
99581
  import { promisify as promisify3 } from "node:util";
98495
- var log78, scryptAsync, FeishuAdapter;
99582
+ var log80, scryptAsync, FeishuAdapter;
98496
99583
  var init_adapter = __esm({
98497
99584
  "../comms/dist/feishu/adapter.js"() {
98498
99585
  "use strict";
98499
99586
  init_dist();
98500
99587
  init_client();
98501
- log78 = createLogger("feishu-adapter");
99588
+ log80 = createLogger("feishu-adapter");
98502
99589
  scryptAsync = promisify3(scrypt);
98503
99590
  FeishuAdapter = class {
98504
99591
  platform = "feishu";
@@ -98519,10 +99606,10 @@ var init_adapter = __esm({
98519
99606
  const port = this.config.webhookPort ?? 9e3;
98520
99607
  this.server = createServer3((req, res) => this.handleWebhook(req, res));
98521
99608
  this.server.listen(port, () => {
98522
- log78.info(`Feishu webhook server listening on port ${port}`);
99609
+ log80.info(`Feishu webhook server listening on port ${port}`);
98523
99610
  });
98524
99611
  this.connected = true;
98525
- log78.info("Feishu adapter connected");
99612
+ log80.info("Feishu adapter connected");
98526
99613
  }
98527
99614
  async disconnect() {
98528
99615
  if (this.server) {
@@ -98530,7 +99617,7 @@ var init_adapter = __esm({
98530
99617
  this.server = void 0;
98531
99618
  }
98532
99619
  this.connected = false;
98533
- log78.info("Feishu adapter disconnected");
99620
+ log80.info("Feishu adapter disconnected");
98534
99621
  }
98535
99622
  async sendMessage(channelId, content, options) {
98536
99623
  if (!this.client)
@@ -98606,12 +99693,12 @@ var init_adapter = __esm({
98606
99693
  res.end("ok");
98607
99694
  if (event.header?.event_type === "im.message.receive_v1") {
98608
99695
  this.processMessageEvent(event).catch((err) => {
98609
- log78.error("Failed to process Feishu message event", { error: err.message });
99696
+ log80.error("Failed to process Feishu message event", { error: err.message });
98610
99697
  });
98611
99698
  }
98612
99699
  if (event["action"]) {
98613
99700
  this.processCardAction(event).catch((err) => {
98614
- log78.error("Failed to process card action", { error: err.message });
99701
+ log80.error("Failed to process card action", { error: err.message });
98615
99702
  });
98616
99703
  }
98617
99704
  };
@@ -98622,14 +99709,14 @@ var init_adapter = __esm({
98622
99709
  const event = JSON.parse(decrypted);
98623
99710
  processEvent(event);
98624
99711
  } else if (raw.encrypt && !this.config?.encryptKey) {
98625
- log78.warn("Received encrypted Feishu payload but no encryptKey configured");
99712
+ log80.warn("Received encrypted Feishu payload but no encryptKey configured");
98626
99713
  res.writeHead(200);
98627
99714
  res.end("ok");
98628
99715
  } else {
98629
99716
  processEvent(raw);
98630
99717
  }
98631
99718
  } catch (err) {
98632
- log78.error("Failed to process Feishu webhook", { error: err instanceof Error ? err.message : String(err) });
99719
+ log80.error("Failed to process Feishu webhook", { error: err instanceof Error ? err.message : String(err) });
98633
99720
  res.writeHead(400);
98634
99721
  res.end("bad request");
98635
99722
  }
@@ -98674,7 +99761,7 @@ var init_adapter = __esm({
98674
99761
  try {
98675
99762
  await handler4(message);
98676
99763
  } catch (error) {
98677
- log78.error("Message handler failed", { error });
99764
+ log80.error("Message handler failed", { error });
98678
99765
  }
98679
99766
  }
98680
99767
  }
@@ -98709,7 +99796,7 @@ var init_adapter = __esm({
98709
99796
  try {
98710
99797
  await handler4(message);
98711
99798
  } catch (error) {
98712
- log78.error("Card action handler failed", { error });
99799
+ log80.error("Card action handler failed", { error });
98713
99800
  }
98714
99801
  }
98715
99802
  }
@@ -98726,12 +99813,12 @@ var init_cards = __esm({
98726
99813
 
98727
99814
  // ../comms/dist/webui/adapter.js
98728
99815
  import { createServer as createServer4 } from "node:http";
98729
- var log79, WebUIAdapter;
99816
+ var log81, WebUIAdapter;
98730
99817
  var init_adapter2 = __esm({
98731
99818
  "../comms/dist/webui/adapter.js"() {
98732
99819
  "use strict";
98733
99820
  init_dist();
98734
- log79 = createLogger("webui-adapter");
99821
+ log81 = createLogger("webui-adapter");
98735
99822
  WebUIAdapter = class {
98736
99823
  platform = "webui";
98737
99824
  handlers = [];
@@ -98743,7 +99830,7 @@ var init_adapter2 = __esm({
98743
99830
  this.port = config["port"] ?? 8058;
98744
99831
  this.server = createServer4((req, res) => this.handleRequest(req, res));
98745
99832
  this.server.listen(this.port, "0.0.0.0", () => {
98746
- log79.info(`WebUI comm server listening on 0.0.0.0:${this.port}`);
99833
+ log81.info(`WebUI comm server listening on 0.0.0.0:${this.port}`);
98747
99834
  });
98748
99835
  this.connected = true;
98749
99836
  }
@@ -98816,7 +99903,7 @@ var init_adapter2 = __esm({
98816
99903
  res.end(JSON.stringify({ received: true, messageId: message.id }));
98817
99904
  for (const handler4 of this.handlers) {
98818
99905
  handler4(message).catch((err) => {
98819
- log79.error("WebUI message handler failed", { error: String(err) });
99906
+ log81.error("WebUI message handler failed", { error: String(err) });
98820
99907
  });
98821
99908
  }
98822
99909
  } catch (error) {
@@ -98830,87 +99917,87 @@ var init_adapter2 = __esm({
98830
99917
  });
98831
99918
 
98832
99919
  // ../comms/dist/whatsapp/client.js
98833
- var log80;
99920
+ var log82;
98834
99921
  var init_client2 = __esm({
98835
99922
  "../comms/dist/whatsapp/client.js"() {
98836
99923
  "use strict";
98837
99924
  init_dist();
98838
- log80 = createLogger("whatsapp-client");
99925
+ log82 = createLogger("whatsapp-client");
98839
99926
  }
98840
99927
  });
98841
99928
 
98842
99929
  // ../comms/dist/whatsapp/adapter.js
98843
- var log81;
99930
+ var log83;
98844
99931
  var init_adapter3 = __esm({
98845
99932
  "../comms/dist/whatsapp/adapter.js"() {
98846
99933
  "use strict";
98847
99934
  init_dist();
98848
99935
  init_client2();
98849
- log81 = createLogger("whatsapp-adapter");
99936
+ log83 = createLogger("whatsapp-adapter");
98850
99937
  }
98851
99938
  });
98852
99939
 
98853
99940
  // ../comms/dist/slack/client.js
98854
- var log82;
99941
+ var log84;
98855
99942
  var init_client3 = __esm({
98856
99943
  "../comms/dist/slack/client.js"() {
98857
99944
  "use strict";
98858
99945
  init_dist();
98859
- log82 = createLogger("slack-client");
99946
+ log84 = createLogger("slack-client");
98860
99947
  }
98861
99948
  });
98862
99949
 
98863
99950
  // ../comms/dist/slack/adapter.js
98864
- var log83;
99951
+ var log85;
98865
99952
  var init_adapter4 = __esm({
98866
99953
  "../comms/dist/slack/adapter.js"() {
98867
99954
  "use strict";
98868
99955
  init_dist();
98869
99956
  init_client3();
98870
- log83 = createLogger("slack-adapter");
99957
+ log85 = createLogger("slack-adapter");
98871
99958
  }
98872
99959
  });
98873
99960
 
98874
99961
  // ../comms/dist/telegram/client.js
98875
- var log84;
99962
+ var log86;
98876
99963
  var init_client4 = __esm({
98877
99964
  "../comms/dist/telegram/client.js"() {
98878
99965
  "use strict";
98879
99966
  init_dist();
98880
- log84 = createLogger("telegram-client");
99967
+ log86 = createLogger("telegram-client");
98881
99968
  }
98882
99969
  });
98883
99970
 
98884
99971
  // ../comms/dist/telegram/adapter.js
98885
- var log85;
99972
+ var log87;
98886
99973
  var init_adapter5 = __esm({
98887
99974
  "../comms/dist/telegram/adapter.js"() {
98888
99975
  "use strict";
98889
99976
  init_dist();
98890
99977
  init_client4();
98891
- log85 = createLogger("telegram-adapter");
99978
+ log87 = createLogger("telegram-adapter");
98892
99979
  }
98893
99980
  });
98894
99981
 
98895
99982
  // ../comms/dist/router.js
98896
- var log86, MessageRouter;
99983
+ var log88, MessageRouter;
98897
99984
  var init_router2 = __esm({
98898
99985
  "../comms/dist/router.js"() {
98899
99986
  "use strict";
98900
99987
  init_dist();
98901
- log86 = createLogger("message-router");
99988
+ log88 = createLogger("message-router");
98902
99989
  MessageRouter = class {
98903
99990
  adapters = /* @__PURE__ */ new Map();
98904
99991
  agentChannelMap = /* @__PURE__ */ new Map();
98905
99992
  agentHandler;
98906
99993
  registerAdapter(adapter2) {
98907
99994
  this.adapters.set(adapter2.platform, adapter2);
98908
- log86.info(`Registered comm adapter: ${adapter2.platform}`);
99995
+ log88.info(`Registered comm adapter: ${adapter2.platform}`);
98909
99996
  }
98910
- bindAgentToChannel(agentId2, platform4, channelId) {
98911
- const key2 = `${platform4}:${channelId}`;
99997
+ bindAgentToChannel(agentId2, platform5, channelId) {
99998
+ const key2 = `${platform5}:${channelId}`;
98912
99999
  this.agentChannelMap.set(key2, agentId2);
98913
- log86.info(`Bound agent ${agentId2} to ${key2}`);
100000
+ log88.info(`Bound agent ${agentId2} to ${key2}`);
98914
100001
  }
98915
100002
  setAgentHandler(handler4) {
98916
100003
  this.agentHandler = handler4;
@@ -98919,7 +100006,7 @@ var init_router2 = __esm({
98919
100006
  for (const config of configs) {
98920
100007
  const adapter2 = this.adapters.get(config.platform);
98921
100008
  if (!adapter2) {
98922
- log86.warn(`No adapter registered for platform: ${config.platform}`);
100009
+ log88.warn(`No adapter registered for platform: ${config.platform}`);
98923
100010
  continue;
98924
100011
  }
98925
100012
  await adapter2.connect(config);
@@ -98935,22 +100022,22 @@ var init_router2 = __esm({
98935
100022
  }
98936
100023
  }
98937
100024
  }
98938
- async sendToChannel(platform4, channelId, content) {
98939
- const adapter2 = this.adapters.get(platform4);
100025
+ async sendToChannel(platform5, channelId, content) {
100026
+ const adapter2 = this.adapters.get(platform5);
98940
100027
  if (!adapter2 || !adapter2.isConnected()) {
98941
- log86.warn(`Adapter not available for platform: ${platform4}`);
100028
+ log88.warn(`Adapter not available for platform: ${platform5}`);
98942
100029
  return void 0;
98943
100030
  }
98944
100031
  return adapter2.sendMessage(channelId, content);
98945
100032
  }
98946
- async sendAsAgent(agentId2, platform4, channelId, content) {
98947
- return this.sendToChannel(platform4, channelId, content);
100033
+ async sendAsAgent(agentId2, platform5, channelId, content) {
100034
+ return this.sendToChannel(platform5, channelId, content);
98948
100035
  }
98949
100036
  async routeIncomingMessage(message) {
98950
100037
  const key2 = `${message.platform}:${message.channelId}`;
98951
100038
  const agentId2 = message.agentId || this.agentChannelMap.get(key2);
98952
100039
  if (!agentId2) {
98953
- log86.debug("No agent bound to channel, skipping message", { key: key2 });
100040
+ log88.debug("No agent bound to channel, skipping message", { key: key2 });
98954
100041
  return;
98955
100042
  }
98956
100043
  message.agentId = agentId2;
@@ -98966,7 +100053,7 @@ var init_router2 = __esm({
98966
100053
  }
98967
100054
  }
98968
100055
  } catch (error) {
98969
- log86.error("Agent handler failed", { agentId: agentId2, error: String(error) });
100056
+ log88.error("Agent handler failed", { agentId: agentId2, error: String(error) });
98970
100057
  }
98971
100058
  }
98972
100059
  }
@@ -98993,17 +100080,17 @@ var init_dist7 = __esm({
98993
100080
  });
98994
100081
 
98995
100082
  // src/utils/logger.ts
98996
- import { createWriteStream as createWriteStream2, existsSync as existsSync33, mkdirSync as mkdirSync24, appendFileSync as appendFileSync3 } from "node:fs";
98997
- import { join as join29 } from "node:path";
98998
- import { homedir as homedir18 } from "node:os";
100083
+ import { createWriteStream as createWriteStream2, existsSync as existsSync35, mkdirSync as mkdirSync26, appendFileSync as appendFileSync3 } from "node:fs";
100084
+ import { join as join31 } from "node:path";
100085
+ import { homedir as homedir20 } from "node:os";
98999
100086
  function ensureLogDir2() {
99000
- if (!existsSync33(LOG_DIR2)) {
99001
- mkdirSync24(LOG_DIR2, { recursive: true, mode: 493 });
100087
+ if (!existsSync35(LOG_DIR2)) {
100088
+ mkdirSync26(LOG_DIR2, { recursive: true, mode: 493 });
99002
100089
  }
99003
100090
  }
99004
100091
  function getStartupLogPath() {
99005
100092
  const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
99006
- return join29(LOG_DIR2, `startup-${date}.log`);
100093
+ return join31(LOG_DIR2, `startup-${date}.log`);
99007
100094
  }
99008
100095
  function setSuppressConsole(suppress) {
99009
100096
  _suppressConsole = suppress;
@@ -99058,7 +100145,7 @@ var LOG_DIR2, startupLogStream, startupLogPath, _suppressConsole, LEVEL_PREFIX;
99058
100145
  var init_logger2 = __esm({
99059
100146
  "src/utils/logger.ts"() {
99060
100147
  "use strict";
99061
- LOG_DIR2 = join29(homedir18(), ".markus", "logs");
100148
+ LOG_DIR2 = join31(homedir20(), ".markus", "logs");
99062
100149
  startupLogStream = null;
99063
100150
  startupLogPath = "";
99064
100151
  _suppressConsole = false;
@@ -99076,10 +100163,10 @@ var init_logger2 = __esm({
99076
100163
  // src/utils/browser.ts
99077
100164
  import { exec } from "node:child_process";
99078
100165
  import { get as httpGet } from "node:http";
99079
- import { platform as platform3 } from "node:os";
100166
+ import { platform as platform4 } from "node:os";
99080
100167
  function openBrowser(url) {
99081
100168
  if (process.env["NO_BROWSER"]) return;
99082
- const sys = platform3();
100169
+ const sys = platform4();
99083
100170
  const cmd = sys === "darwin" ? `open "${url}"` : sys === "win32" ? `start "" "${url}"` : `xdg-open "${url}"`;
99084
100171
  exec(cmd, (err) => {
99085
100172
  if (err) {
@@ -99114,9 +100201,9 @@ var init_browser = __esm({
99114
100201
  });
99115
100202
 
99116
100203
  // src/utils/startupProgress.ts
99117
- import { homedir as homedir19 } from "node:os";
99118
- import { appendFileSync as appendFileSync4, existsSync as existsSync34, mkdirSync as mkdirSync25 } from "node:fs";
99119
- import { join as join30 } from "node:path";
100204
+ import { homedir as homedir21 } from "node:os";
100205
+ import { appendFileSync as appendFileSync4, existsSync as existsSync36, mkdirSync as mkdirSync27 } from "node:fs";
100206
+ import { join as join32 } from "node:path";
99120
100207
  function clearScreen() {
99121
100208
  return "\x1B[2J\x1B[H";
99122
100209
  }
@@ -99278,8 +100365,8 @@ var init_startupProgress = __esm({
99278
100365
  const line = `${ts} ${msg}
99279
100366
  `;
99280
100367
  try {
99281
- const dir = join30(homedir19(), ".markus", "logs");
99282
- if (!existsSync34(dir)) mkdirSync25(dir, { recursive: true, mode: 493 });
100368
+ const dir = join32(homedir21(), ".markus", "logs");
100369
+ if (!existsSync36(dir)) mkdirSync27(dir, { recursive: true, mode: 493 });
99283
100370
  appendFileSync4(this.logPath, line, { mode: 420 });
99284
100371
  } catch {
99285
100372
  }
@@ -99388,13 +100475,13 @@ var init_startupProgress = __esm({
99388
100475
  });
99389
100476
 
99390
100477
  // src/connector-service.ts
99391
- import { resolve as resolve16, join as join31, dirname as dirname10 } from "node:path";
99392
- import { existsSync as existsSync35, readFileSync as readFileSync24, writeFileSync as writeFileSync20, mkdirSync as mkdirSync26, readdirSync as readdirSync13, cpSync as cpSync4 } from "node:fs";
99393
- import { homedir as homedir20 } from "node:os";
100478
+ import { resolve as resolve16, join as join33, dirname as dirname12 } from "node:path";
100479
+ import { existsSync as existsSync37, readFileSync as readFileSync26, writeFileSync as writeFileSync22, mkdirSync as mkdirSync28, readdirSync as readdirSync13, cpSync as cpSync4 } from "node:fs";
100480
+ import { homedir as homedir22 } from "node:os";
99394
100481
  import { execSync as execSync4 } from "node:child_process";
99395
100482
  import { fileURLToPath as fileURLToPath6 } from "node:url";
99396
100483
  function expandHome(p) {
99397
- return p.replace(/^~/, homedir20());
100484
+ return p.replace(/^~/, homedir22());
99398
100485
  }
99399
100486
  function loadConnectors() {
99400
100487
  const connectors = /* @__PURE__ */ new Map();
@@ -99402,16 +100489,16 @@ function loadConnectors() {
99402
100489
  loadFromDir(builtinDir, connectors);
99403
100490
  const devDir = resolve16(process.cwd(), "packages", "cli", "connectors");
99404
100491
  if (devDir !== builtinDir) loadFromDir(devDir, connectors);
99405
- const userDir = join31(homedir20(), ".markus", "connectors");
100492
+ const userDir = join33(homedir22(), ".markus", "connectors");
99406
100493
  loadFromDir(userDir, connectors);
99407
100494
  return [...connectors.values()].filter((c) => c.platform !== "_template");
99408
100495
  }
99409
100496
  function loadFromDir(dir, map) {
99410
- if (!existsSync35(dir)) return;
100497
+ if (!existsSync37(dir)) return;
99411
100498
  for (const file of readdirSync13(dir)) {
99412
100499
  if (!file.endsWith(".json") || file.startsWith("_")) continue;
99413
100500
  try {
99414
- const raw = readFileSync24(join31(dir, file), "utf-8");
100501
+ const raw = readFileSync26(join33(dir, file), "utf-8");
99415
100502
  const desc = JSON.parse(raw);
99416
100503
  if (desc.platform) {
99417
100504
  map.set(desc.platform, desc);
@@ -99420,8 +100507,8 @@ function loadFromDir(dir, map) {
99420
100507
  }
99421
100508
  }
99422
100509
  }
99423
- function findConnector(platform4) {
99424
- return loadConnectors().find((c) => c.platform === platform4);
100510
+ function findConnector(platform5) {
100511
+ return loadConnectors().find((c) => c.platform === platform5);
99425
100512
  }
99426
100513
  function scanInstalledPlatforms() {
99427
100514
  const connectors = loadConnectors();
@@ -99436,7 +100523,7 @@ function scanInstalledPlatforms() {
99436
100523
  };
99437
100524
  for (const p of c.detection.configPaths) {
99438
100525
  const expanded = expandHome(p);
99439
- if (existsSync35(expanded)) {
100526
+ if (existsSync37(expanded)) {
99440
100527
  result.installed = true;
99441
100528
  result.configPath = expanded;
99442
100529
  break;
@@ -99463,9 +100550,9 @@ function scanInstalledPlatforms() {
99463
100550
  }
99464
100551
  function readPlatformConfig(connector) {
99465
100552
  const configPath = expandHome(connector.integration.configPath);
99466
- if (!existsSync35(configPath)) return null;
100553
+ if (!existsSync37(configPath)) return null;
99467
100554
  try {
99468
- const raw = readFileSync24(configPath, "utf-8");
100555
+ const raw = readFileSync26(configPath, "utf-8");
99469
100556
  if (connector.integration.configFormat === "json5") {
99470
100557
  const cleaned = raw.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "").replace(/,\s*([\]}])/g, "$1");
99471
100558
  return JSON.parse(cleaned);
@@ -99477,12 +100564,12 @@ function readPlatformConfig(connector) {
99477
100564
  }
99478
100565
  function writePlatformConfig(connector, markusUrl, token) {
99479
100566
  const configPath = expandHome(connector.integration.configPath);
99480
- const configDir = dirname10(configPath);
99481
- if (!existsSync35(configDir)) {
99482
- mkdirSync26(configDir, { recursive: true });
100567
+ const configDir = dirname12(configPath);
100568
+ if (!existsSync37(configDir)) {
100569
+ mkdirSync28(configDir, { recursive: true });
99483
100570
  }
99484
100571
  let config = {};
99485
- if (existsSync35(configPath)) {
100572
+ if (existsSync37(configPath)) {
99486
100573
  const existing = readPlatformConfig(connector);
99487
100574
  if (existing) config = existing;
99488
100575
  }
@@ -99490,7 +100577,7 @@ function writePlatformConfig(connector, markusUrl, token) {
99490
100577
  setNestedField(config, connector.integration.tokenField, token);
99491
100578
  try {
99492
100579
  const content = JSON.stringify(config, null, 2);
99493
- writeFileSync20(configPath, content, "utf-8");
100580
+ writeFileSync22(configPath, content, "utf-8");
99494
100581
  return true;
99495
100582
  } catch {
99496
100583
  return false;
@@ -99503,21 +100590,21 @@ function installSkillTemplate(connector) {
99503
100590
  const skillDir = expandHome(connector.integration.skillDir);
99504
100591
  const templateName = connector.integration.skillTemplateName;
99505
100592
  const candidates = [
99506
- join31(homedir20(), ".markus", "templates", templateName),
100593
+ join33(homedir22(), ".markus", "templates", templateName),
99507
100594
  resolve16(process.cwd(), "templates", templateName),
99508
100595
  resolve16(__dirname5, "..", "templates", templateName)
99509
100596
  ];
99510
100597
  let sourceDir;
99511
100598
  for (const c of candidates) {
99512
- if (existsSync35(c)) {
100599
+ if (existsSync37(c)) {
99513
100600
  sourceDir = c;
99514
100601
  break;
99515
100602
  }
99516
100603
  }
99517
100604
  if (!sourceDir) return false;
99518
- const targetDir = join31(skillDir, templateName);
99519
- if (!existsSync35(targetDir)) {
99520
- mkdirSync26(targetDir, { recursive: true });
100605
+ const targetDir = join33(skillDir, templateName);
100606
+ if (!existsSync37(targetDir)) {
100607
+ mkdirSync28(targetDir, { recursive: true });
99521
100608
  }
99522
100609
  try {
99523
100610
  cpSync4(sourceDir, targetDir, { recursive: true });
@@ -99559,7 +100646,7 @@ var init_connector_service = __esm({
99559
100646
  "src/connector-service.ts"() {
99560
100647
  "use strict";
99561
100648
  __filename4 = fileURLToPath6(import.meta.url);
99562
- __dirname5 = dirname10(__filename4);
100649
+ __dirname5 = dirname12(__filename4);
99563
100650
  }
99564
100651
  });
99565
100652
 
@@ -99570,8 +100657,8 @@ __export(init_exports, {
99570
100657
  registerInitCommand: () => registerInitCommand
99571
100658
  });
99572
100659
  import { resolve as resolve17 } from "node:path";
99573
- import { readFileSync as readFileSync25, existsSync as existsSync36, cpSync as cpSync5 } from "node:fs";
99574
- import { homedir as homedir21 } from "node:os";
100660
+ import { readFileSync as readFileSync27, existsSync as existsSync38, cpSync as cpSync5 } from "node:fs";
100661
+ import { homedir as homedir23 } from "node:os";
99575
100662
  function registerInitCommand(program2) {
99576
100663
  program2.command("init").description("Setup wizard: configure LLM provider, API keys, and server settings").option("--force", "Overwrite existing configuration").option("--non-interactive", "Run without prompts (use env vars or --import-from)").option("--provider <name>", "LLM provider (anthropic/openai/google/minimax/siliconflow/zai/deepseek/ollama)").option("--api-key <key>", "LLM API key").option("--port <port>", "API server port", "8056").option("--import-from <platform>", "Import LLM config from an installed agent platform (e.g. openclaw, hermes)").option("--auto-connect", "Auto-connect detected agent platforms after init").action(async (opts) => {
99577
100664
  await quickInit({
@@ -99586,7 +100673,7 @@ function registerInitCommand(program2) {
99586
100673
  });
99587
100674
  }
99588
100675
  async function quickInit(options) {
99589
- const { writeFileSync: writeFileSync21, mkdirSync: mkdirSync27 } = await import("node:fs");
100676
+ const { writeFileSync: writeFileSync23, mkdirSync: mkdirSync29 } = await import("node:fs");
99590
100677
  const { join: pathJoin } = await import("node:path");
99591
100678
  const readline3 = await import("node:readline");
99592
100679
  const nonInteractive = options?.nonInteractive ?? false;
@@ -99607,7 +100694,7 @@ async function quickInit(options) {
99607
100694
  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
99608
100695
  `);
99609
100696
  const configPath = getDefaultConfigPath();
99610
- if (existsSync36(configPath) && !options?.force) {
100697
+ if (existsSync38(configPath) && !options?.force) {
99611
100698
  if (nonInteractive) {
99612
100699
  console.log(` Existing configuration found. Use --force to overwrite.`);
99613
100700
  rl?.close();
@@ -99640,11 +100727,11 @@ async function quickInit(options) {
99640
100727
  const installedPlatforms = scanInstalledPlatforms().filter((p) => p.installed);
99641
100728
  let openclawPath = "";
99642
100729
  const openclawCandidates = [
99643
- pathJoin(homedir21(), ".openclaw", "openclaw.json"),
99644
- pathJoin(homedir21(), ".openclaw", "openclaw.json5")
100730
+ pathJoin(homedir23(), ".openclaw", "openclaw.json"),
100731
+ pathJoin(homedir23(), ".openclaw", "openclaw.json5")
99645
100732
  ];
99646
100733
  for (const p of openclawCandidates) {
99647
- if (existsSync36(p)) {
100734
+ if (existsSync38(p)) {
99648
100735
  openclawPath = p;
99649
100736
  break;
99650
100737
  }
@@ -99718,7 +100805,7 @@ async function quickInit(options) {
99718
100805
  }
99719
100806
  } else if (mode === "openclaw") {
99720
100807
  try {
99721
- const raw = readFileSync25(openclawPath, "utf-8");
100808
+ const raw = readFileSync27(openclawPath, "utf-8");
99722
100809
  const cleaned = raw.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "").replace(/,\s*([\]}])/g, "$1");
99723
100810
  const parsed = JSON.parse(cleaned);
99724
100811
  const modelsSection = parsed.models;
@@ -99846,18 +100933,18 @@ async function quickInit(options) {
99846
100933
  console.error(`
99847
100934
  Failed to save config: ${e}`);
99848
100935
  }
99849
- const userTemplatesDir = pathJoin(homedir21(), ".markus", "templates");
100936
+ const userTemplatesDir = pathJoin(homedir23(), ".markus", "templates");
99850
100937
  const builtinTemplatesDir = resolveTemplatesDir("roles");
99851
- if (builtinTemplatesDir && existsSync36(builtinTemplatesDir) && !existsSync36(userTemplatesDir)) {
100938
+ if (builtinTemplatesDir && existsSync38(builtinTemplatesDir) && !existsSync38(userTemplatesDir)) {
99852
100939
  const builtinRoot = resolve17(builtinTemplatesDir, "..");
99853
- mkdirSync27(userTemplatesDir, { recursive: true });
100940
+ mkdirSync29(userTemplatesDir, { recursive: true });
99854
100941
  cpSync5(builtinRoot, userTemplatesDir, { recursive: true });
99855
100942
  console.log(` Copied templates to ${userTemplatesDir}`);
99856
100943
  }
99857
100944
  const devRoleDir = pathJoin(userTemplatesDir || pathJoin(process.cwd(), "templates"), "roles", "developer");
99858
- if (!existsSync36(devRoleDir)) {
99859
- mkdirSync27(devRoleDir, { recursive: true });
99860
- writeFileSync21(
100945
+ if (!existsSync38(devRoleDir)) {
100946
+ mkdirSync29(devRoleDir, { recursive: true });
100947
+ writeFileSync23(
99861
100948
  pathJoin(devRoleDir, "ROLE.md"),
99862
100949
  [
99863
100950
  "---",
@@ -99899,7 +100986,7 @@ async function quickInit(options) {
99899
100986
  console.log("");
99900
100987
  }
99901
100988
  console.log(` Config: ${configPath}`);
99902
- console.log(` Data: ${pathJoin(homedir21(), ".markus")}`);
100989
+ console.log(` Data: ${pathJoin(homedir23(), ".markus")}`);
99903
100990
  console.log(` Server: http://localhost:${apiPort}`);
99904
100991
  console.log("");
99905
100992
  }
@@ -99937,12 +101024,12 @@ function signJwt(payload, secret) {
99937
101024
  const sig = base64url(createHmac2("sha256", secret).update(`${header}.${body}`).digest());
99938
101025
  return `${header}.${body}.${sig}`;
99939
101026
  }
99940
- var log87, _rtcModule, STUN_SERVERS, RECONNECT_BASE_MS, RECONNECT_MAX_MS, HEARTBEAT_INTERVAL_MS, PEER_PING_INTERVAL_MS, PEER_PING_TIMEOUT_MS, RELAY_INACTIVITY_TIMEOUT_MS, RemoteAccessAgent;
101027
+ var log89, _rtcModule, STUN_SERVERS, RECONNECT_BASE_MS, RECONNECT_MAX_MS, HEARTBEAT_INTERVAL_MS2, PEER_PING_INTERVAL_MS, PEER_PING_TIMEOUT_MS, RELAY_INACTIVITY_TIMEOUT_MS, RemoteAccessAgent;
99941
101028
  var init_agent3 = __esm({
99942
101029
  "../remote/dist/agent.js"() {
99943
101030
  "use strict";
99944
101031
  init_dist();
99945
- log87 = createLogger("remote");
101032
+ log89 = createLogger("remote");
99946
101033
  _rtcModule = null;
99947
101034
  STUN_SERVERS = [
99948
101035
  "stun:stun.l.google.com:19302",
@@ -99950,7 +101037,7 @@ var init_agent3 = __esm({
99950
101037
  ];
99951
101038
  RECONNECT_BASE_MS = 2e3;
99952
101039
  RECONNECT_MAX_MS = 6e4;
99953
- HEARTBEAT_INTERVAL_MS = 25e3;
101040
+ HEARTBEAT_INTERVAL_MS2 = 25e3;
99954
101041
  PEER_PING_INTERVAL_MS = 15e3;
99955
101042
  PEER_PING_TIMEOUT_MS = 1e4;
99956
101043
  RELAY_INACTIVITY_TIMEOUT_MS = 5 * 6e4;
@@ -99971,19 +101058,19 @@ var init_agent3 = __esm({
99971
101058
  // ── Public API ────────────────────────────────────────────────────────────
99972
101059
  async start() {
99973
101060
  this.destroyed = false;
99974
- log87.info("Starting remote access agent...");
101061
+ log89.info("Starting remote access agent...");
99975
101062
  const rtc = await loadRtcModule();
99976
101063
  rtc.initLogger("Warning");
99977
101064
  await this.discoverLocalOwner();
99978
101065
  try {
99979
101066
  this.registration = await this.registerInstance();
99980
- log87.info("Registered with Hub", {
101067
+ log89.info("Registered with Hub", {
99981
101068
  instanceId: this.registration.instanceId,
99982
101069
  remoteUrl: this.registration.remoteUrl
99983
101070
  });
99984
101071
  this.connectSignaling();
99985
101072
  } catch (err) {
99986
- log87.error("Failed to register with Hub", { error: String(err) });
101073
+ log89.error("Failed to register with Hub", { error: String(err) });
99987
101074
  this.scheduleReconnect();
99988
101075
  }
99989
101076
  }
@@ -100004,14 +101091,14 @@ var init_agent3 = __esm({
100004
101091
  if (users?.length) {
100005
101092
  const owner = users.find((u) => u.role === "owner") ?? users[0];
100006
101093
  this.localOwnerUserId = owner.id;
100007
- log87.info("Discovered local owner", { userId: this.localOwnerUserId });
101094
+ log89.info("Discovered local owner", { userId: this.localOwnerUserId });
100008
101095
  }
100009
101096
  return;
100010
101097
  } catch (err) {
100011
101098
  if (attempt < 2) {
100012
101099
  await new Promise((r) => setTimeout(r, 1e3 * (attempt + 1)));
100013
101100
  } else {
100014
- log87.warn("Failed to discover local owner, using synthetic user", { error: String(err) });
101101
+ log89.warn("Failed to discover local owner, using synthetic user", { error: String(err) });
100015
101102
  }
100016
101103
  }
100017
101104
  }
@@ -100043,7 +101130,7 @@ var init_agent3 = __esm({
100043
101130
  this.registration = null;
100044
101131
  }
100045
101132
  this.emitStatus();
100046
- log87.info("Remote access agent stopped");
101133
+ log89.info("Remote access agent stopped");
100047
101134
  }
100048
101135
  getStatus() {
100049
101136
  const wsOpen = this.ws?.readyState === WebSocket2.OPEN;
@@ -100152,11 +101239,11 @@ var init_agent3 = __esm({
100152
101239
  return;
100153
101240
  const { signalUrl, signalingToken } = this.registration;
100154
101241
  const wsUrl = `${signalUrl}?token=${encodeURIComponent(signalingToken)}`;
100155
- log87.info("Connecting to signal server...", { signalUrl });
101242
+ log89.info("Connecting to signal server...", { signalUrl });
100156
101243
  const ws = new WebSocket2(wsUrl);
100157
101244
  this.ws = ws;
100158
101245
  ws.on("open", () => {
100159
- log87.info("Signal server connected");
101246
+ log89.info("Signal server connected");
100160
101247
  this.reconnectAttempts = 0;
100161
101248
  this.startHeartbeat();
100162
101249
  this.emitStatus();
@@ -100167,11 +101254,11 @@ var init_agent3 = __esm({
100167
101254
  const msg = JSON.parse(data.toString());
100168
101255
  this.handleSignalingMessage(msg);
100169
101256
  } catch (err) {
100170
- log87.warn("Invalid signaling message", { error: String(err) });
101257
+ log89.warn("Invalid signaling message", { error: String(err) });
100171
101258
  }
100172
101259
  });
100173
101260
  ws.on("close", (code) => {
100174
- log87.warn("Signal server disconnected", { code });
101261
+ log89.warn("Signal server disconnected", { code });
100175
101262
  this.stopHeartbeat();
100176
101263
  this.ws = null;
100177
101264
  this.emitStatus();
@@ -100179,7 +101266,7 @@ var init_agent3 = __esm({
100179
101266
  this.scheduleReconnect();
100180
101267
  });
100181
101268
  ws.on("error", (err) => {
100182
- log87.error("Signal server error", { error: err.message });
101269
+ log89.error("Signal server error", { error: err.message });
100183
101270
  });
100184
101271
  }
100185
101272
  handleSignalingMessage(msg) {
@@ -100190,7 +101277,7 @@ var init_agent3 = __esm({
100190
101277
  this.send({ type: "pong" });
100191
101278
  break;
100192
101279
  case "registered":
100193
- log87.info("Registered with signal server", { instanceId: msg["instanceId"] });
101280
+ log89.info("Registered with signal server", { instanceId: msg["instanceId"] });
100194
101281
  break;
100195
101282
  case "peer_request":
100196
101283
  if (peerId)
@@ -100212,7 +101299,7 @@ var init_agent3 = __esm({
100212
101299
  break;
100213
101300
  case "relay_activated":
100214
101301
  if (peerId)
100215
- log87.info("Peer activated relay mode", { peerId });
101302
+ log89.info("Peer activated relay mode", { peerId });
100216
101303
  break;
100217
101304
  case "relay_frame":
100218
101305
  if (peerId && msg["data"]) {
@@ -100220,21 +101307,21 @@ var init_agent3 = __esm({
100220
101307
  }
100221
101308
  break;
100222
101309
  default:
100223
- log87.debug("Unknown signaling message type", { type });
101310
+ log89.debug("Unknown signaling message type", { type });
100224
101311
  }
100225
101312
  }
100226
101313
  // ── WebRTC Peer Connections ───────────────────────────────────────────────
100227
101314
  handlePeerRequest(peerId) {
100228
- log87.info("Peer connection requested", { peerId });
101315
+ log89.info("Peer connection requested", { peerId });
100229
101316
  this.createPeerConnection(peerId);
100230
101317
  }
100231
101318
  handleOffer(peerId, sdp) {
100232
101319
  let session = this.peers.get(peerId);
100233
101320
  if (!session) {
100234
- log87.info("Received offer, creating new peer connection", { peerId });
101321
+ log89.info("Received offer, creating new peer connection", { peerId });
100235
101322
  session = this.createPeerConnection(peerId);
100236
101323
  } else if (!session.pc) {
100237
- log87.info("Received offer for relay-only peer, upgrading to P2P", { peerId });
101324
+ log89.info("Received offer for relay-only peer, upgrading to P2P", { peerId });
100238
101325
  const newSession = this.createPeerConnection(peerId);
100239
101326
  newSession.markusToken = session.markusToken;
100240
101327
  newSession.connectedAt = session.connectedAt;
@@ -100243,14 +101330,14 @@ var init_agent3 = __esm({
100243
101330
  clearInterval(session.pingTimer);
100244
101331
  session = newSession;
100245
101332
  } else {
100246
- log87.info("Received offer for existing peer (ICE restart)", { peerId });
101333
+ log89.info("Received offer for existing peer (ICE restart)", { peerId });
100247
101334
  }
100248
101335
  session.pc.setRemoteDescription(sdp, getRtcModule().DescriptionType.Offer);
100249
101336
  }
100250
101337
  handleIce(peerId, candidate, mid) {
100251
101338
  const session = this.peers.get(peerId);
100252
101339
  if (!session?.pc) {
100253
- log87.warn("Received ICE candidate but no PC", { peerId, hasSession: !!session });
101340
+ log89.warn("Received ICE candidate but no PC", { peerId, hasSession: !!session });
100254
101341
  return;
100255
101342
  }
100256
101343
  session.pc.addRemoteCandidate(candidate, mid ?? "0");
@@ -100289,25 +101376,25 @@ var init_agent3 = __esm({
100289
101376
  const session = { pc, dc: null, pendingChunks: /* @__PURE__ */ new Map(), markusToken: null, connectedAt: now3, lastActiveAt: now3, pingTimer: null, lastPong: now3 };
100290
101377
  this.peers.set(peerId, session);
100291
101378
  pc.onStateChange((state) => {
100292
- log87.info("Peer RTC state", { peerId, state });
101379
+ log89.info("Peer RTC state", { peerId, state });
100293
101380
  if (state === "failed" || state === "closed") {
100294
101381
  this.handlePcFailed(peerId);
100295
101382
  }
100296
101383
  this.emitStatus();
100297
101384
  });
100298
101385
  pc.onGatheringStateChange((state) => {
100299
- log87.info("ICE gathering", { peerId, state });
101386
+ log89.info("ICE gathering", { peerId, state });
100300
101387
  });
100301
101388
  pc.onLocalDescription((sdp, type) => {
100302
- log87.info("Sending local description", { peerId, type });
101389
+ log89.info("Sending local description", { peerId, type });
100303
101390
  this.send({ type, peerId, sdp });
100304
101391
  });
100305
101392
  pc.onLocalCandidate((candidate, mid) => {
100306
- log87.info("Sending ICE candidate", { peerId, candidate: candidate.slice(0, 60) });
101393
+ log89.info("Sending ICE candidate", { peerId, candidate: candidate.slice(0, 60) });
100307
101394
  this.send({ type: "ice", peerId, candidate, mid });
100308
101395
  });
100309
101396
  pc.onDataChannel((dc) => {
100310
- log87.info("DataChannel opened", { peerId, label: dc.getLabel() });
101397
+ log89.info("DataChannel opened", { peerId, label: dc.getLabel() });
100311
101398
  session.dc = dc;
100312
101399
  session.lastPong = Date.now();
100313
101400
  this.emitStatus();
@@ -100317,7 +101404,7 @@ var init_agent3 = __esm({
100317
101404
  this.handleDataChannelMessage(peerId, data);
100318
101405
  });
100319
101406
  dc.onClosed(() => {
100320
- log87.info("DataChannel closed, keeping session for relay", { peerId });
101407
+ log89.info("DataChannel closed, keeping session for relay", { peerId });
100321
101408
  session.dc = null;
100322
101409
  this.emitStatus();
100323
101410
  });
@@ -100328,7 +101415,7 @@ var init_agent3 = __esm({
100328
101415
  const session = this.peers.get(peerId);
100329
101416
  if (!session)
100330
101417
  return;
100331
- log87.info("WebRTC failed, keeping session alive for relay", { peerId });
101418
+ log89.info("WebRTC failed, keeping session alive for relay", { peerId });
100332
101419
  try {
100333
101420
  session.dc?.close();
100334
101421
  } catch {
@@ -100362,7 +101449,7 @@ var init_agent3 = __esm({
100362
101449
  }
100363
101450
  this.peers.delete(peerId);
100364
101451
  this.emitStatus();
100365
- log87.info("Peer cleaned up", { peerId });
101452
+ log89.info("Peer cleaned up", { peerId });
100366
101453
  }
100367
101454
  startPeerPing(peerId, session) {
100368
101455
  if (session.pingTimer)
@@ -100372,13 +101459,13 @@ var init_agent3 = __esm({
100372
101459
  session.pingTimer = setInterval(() => {
100373
101460
  const now3 = Date.now();
100374
101461
  if (now3 - session.lastActiveAt > RELAY_INACTIVITY_TIMEOUT_MS) {
100375
- log87.info("Peer inactive for too long, cleaning up", { peerId });
101462
+ log89.info("Peer inactive for too long, cleaning up", { peerId });
100376
101463
  this.cleanupPeer(peerId);
100377
101464
  return;
100378
101465
  }
100379
101466
  const elapsed = now3 - session.lastPong;
100380
101467
  if (elapsed > PEER_PING_INTERVAL_MS + PEER_PING_TIMEOUT_MS) {
100381
- log87.warn("Peer ping timeout, unresponsive", { peerId, elapsed });
101468
+ log89.warn("Peer ping timeout, unresponsive", { peerId, elapsed });
100382
101469
  this.cleanupPeer(peerId);
100383
101470
  return;
100384
101471
  }
@@ -100419,12 +101506,12 @@ var init_agent3 = __esm({
100419
101506
  this.sendToPeer(peerId, { type: "error", error: `Unknown message type: ${type}` });
100420
101507
  }
100421
101508
  } catch (err) {
100422
- log87.warn("Invalid DataChannel message", { peerId, error: String(err) });
101509
+ log89.warn("Invalid DataChannel message", { peerId, error: String(err) });
100423
101510
  }
100424
101511
  }
100425
101512
  handleRelayFrame(peerId, data) {
100426
101513
  if (!this.peers.has(peerId)) {
100427
- log87.info("Relay frame from unknown peer, creating relay-only session", { peerId });
101514
+ log89.info("Relay frame from unknown peer, creating relay-only session", { peerId });
100428
101515
  const now3 = Date.now();
100429
101516
  this.peers.set(peerId, {
100430
101517
  pc: null,
@@ -100618,14 +101705,14 @@ var init_agent3 = __esm({
100618
101705
  session.dc.sendMessage(data);
100619
101706
  return;
100620
101707
  } catch (err) {
100621
- log87.warn("DataChannel send failed, falling back to relay", { peerId, error: String(err) });
101708
+ log89.warn("DataChannel send failed, falling back to relay", { peerId, error: String(err) });
100622
101709
  }
100623
101710
  }
100624
101711
  if (this.ws?.readyState === WebSocket2.OPEN) {
100625
101712
  this.send({ type: "relay_frame", peerId, data });
100626
101713
  return;
100627
101714
  }
100628
- log87.warn("No transport available for peer", { peerId });
101715
+ log89.warn("No transport available for peer", { peerId });
100629
101716
  }
100630
101717
  send(msg) {
100631
101718
  if (this.ws?.readyState === WebSocket2.OPEN) {
@@ -100636,7 +101723,7 @@ var init_agent3 = __esm({
100636
101723
  this.stopHeartbeat();
100637
101724
  this.heartbeatTimer = setInterval(() => {
100638
101725
  this.send({ type: "pong" });
100639
- }, HEARTBEAT_INTERVAL_MS);
101726
+ }, HEARTBEAT_INTERVAL_MS2);
100640
101727
  }
100641
101728
  stopHeartbeat() {
100642
101729
  if (this.heartbeatTimer) {
@@ -100649,7 +101736,7 @@ var init_agent3 = __esm({
100649
101736
  return;
100650
101737
  const delay = Math.min(RECONNECT_BASE_MS * Math.pow(2, this.reconnectAttempts), RECONNECT_MAX_MS);
100651
101738
  this.reconnectAttempts++;
100652
- log87.info(`Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})...`);
101739
+ log89.info(`Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})...`);
100653
101740
  this.reconnectTimer = setTimeout(() => this.start(), delay);
100654
101741
  }
100655
101742
  emitStatus() {
@@ -100682,15 +101769,15 @@ var start_exports = {};
100682
101769
  __export(start_exports, {
100683
101770
  registerStartCommand: () => registerStartCommand
100684
101771
  });
100685
- import { resolve as resolve18, join as join32, dirname as dirname11 } from "node:path";
100686
- import { existsSync as existsSync37, readFileSync as readFileSync26 } from "node:fs";
100687
- import { homedir as homedir22 } from "node:os";
101772
+ import { resolve as resolve18, join as join34, dirname as dirname13 } from "node:path";
101773
+ import { existsSync as existsSync39, readFileSync as readFileSync28 } from "node:fs";
101774
+ import { homedir as homedir24 } from "node:os";
100688
101775
  function registerStartCommand(program2) {
100689
101776
  program2.command("start").description("Start the Markus server (auto-initializes on first run)").option("--setup", "Force re-run the interactive setup wizard before starting").action(async (opts) => {
100690
101777
  const globalOpts = program2.optsWithGlobals();
100691
101778
  const configPath = globalOpts.config ?? getDefaultConfigPath();
100692
- if (opts.setup || !existsSync37(configPath)) {
100693
- if (!existsSync37(configPath)) {
101779
+ if (opts.setup || !existsSync39(configPath)) {
101780
+ if (!existsSync39(configPath)) {
100694
101781
  console.log(" No configuration found \u2014 auto-configuring from environment...\n");
100695
101782
  }
100696
101783
  const { quickInit: quickInit2 } = await Promise.resolve().then(() => (init_init(), init_exports));
@@ -100819,8 +101906,8 @@ async function createServices(config) {
100819
101906
  extraSkillDirs: skillDirs
100820
101907
  });
100821
101908
  const storage = await initStorage(config.database?.url);
100822
- const markusDataDir = join32(homedir22(), ".markus");
100823
- const sharedDataDir = join32(markusDataDir, "shared");
101909
+ const markusDataDir = join34(homedir24(), ".markus");
101910
+ const sharedDataDir = join34(markusDataDir, "shared");
100824
101911
  const taskService = new TaskService();
100825
101912
  taskService.setSharedDataDir(sharedDataDir);
100826
101913
  if (storage) {
@@ -100852,7 +101939,7 @@ async function createServices(config) {
100852
101939
  const agentManager = new AgentManager({
100853
101940
  llmRouter,
100854
101941
  roleLoader,
100855
- dataDir: join32(markusDataDir, "agents"),
101942
+ dataDir: join34(markusDataDir, "agents"),
100856
101943
  sharedDataDir,
100857
101944
  skillRegistry,
100858
101945
  taskService,
@@ -100892,8 +101979,10 @@ async function createServices(config) {
100892
101979
  const hitlService = new HITLService();
100893
101980
  hitlService.setOrgService(orgService);
100894
101981
  taskService.setHITLService(hitlService);
101982
+ const licenseService = new LicenseService(config.hub?.url);
101983
+ const telemetryService = new TelemetryService(config.hub?.url ?? "https://markus.global", licenseService.getInstanceId());
100895
101984
  const billingService = new BillingService();
100896
- billingService.setOrgPlan("default", "free");
101985
+ billingService.setOrgPlan("default", licenseService.getPlan());
100897
101986
  const auditService = new AuditService();
100898
101987
  taskService.setAuditService(auditService);
100899
101988
  if (storage?.auditRepo) {
@@ -100908,6 +101997,8 @@ async function createServices(config) {
100908
101997
  skillRegistry,
100909
101998
  hitlService,
100910
101999
  billingService,
102000
+ licenseService,
102001
+ telemetryService,
100911
102002
  auditService,
100912
102003
  bootstrapOwnerId
100913
102004
  };
@@ -100967,10 +102058,10 @@ async function startServer(config, values) {
100967
102058
  startupLog("INFO", "\u6B63\u5728\u542F\u52A8\u670D\u52A1...");
100968
102059
  const currentPath = process.env["PATH"] ?? "";
100969
102060
  const extraPaths = [];
100970
- const selfBinDir = dirname11(resolve18(process.argv[1] ?? ""));
102061
+ const selfBinDir = dirname13(resolve18(process.argv[1] ?? ""));
100971
102062
  if (selfBinDir && !currentPath.includes(selfBinDir)) extraPaths.push(selfBinDir);
100972
- const cwdBin = join32(process.cwd(), "node_modules", ".bin");
100973
- if (existsSync37(cwdBin) && !currentPath.includes(cwdBin)) extraPaths.push(cwdBin);
102063
+ const cwdBin = join34(process.cwd(), "node_modules", ".bin");
102064
+ if (existsSync39(cwdBin) && !currentPath.includes(cwdBin)) extraPaths.push(cwdBin);
100974
102065
  if (extraPaths.length > 0) {
100975
102066
  process.env["PATH"] = `${extraPaths.join(":")}:${currentPath}`;
100976
102067
  }
@@ -101013,6 +102104,8 @@ async function startServer(config, values) {
101013
102104
  skillRegistry,
101014
102105
  hitlService,
101015
102106
  billingService,
102107
+ licenseService,
102108
+ telemetryService,
101016
102109
  auditService,
101017
102110
  bootstrapOwnerId
101018
102111
  } = await createServices(config);
@@ -101022,6 +102115,8 @@ async function startServer(config, values) {
101022
102115
  apiServer.setSkillRegistry(skillRegistry);
101023
102116
  apiServer.setHITLService(hitlService);
101024
102117
  apiServer.setBillingService(billingService);
102118
+ apiServer.setLicenseService(licenseService);
102119
+ apiServer.setTelemetryService(telemetryService);
101025
102120
  apiServer.setAuditService(auditService);
101026
102121
  const projectService = new ProjectService();
101027
102122
  const storage = orgService.getStorage();
@@ -101035,7 +102130,7 @@ async function startServer(config, values) {
101035
102130
  projectService.setProjectRepo(storage.projectRepo);
101036
102131
  }
101037
102132
  await projectService.loadFromDB("default");
101038
- const knowledgeStore = new FileKnowledgeStore(join32(homedir22(), ".markus", "knowledge"));
102133
+ const knowledgeStore = new FileKnowledgeStore(join34(homedir24(), ".markus", "knowledge"));
101039
102134
  const knowledgeService = new KnowledgeService(knowledgeStore);
101040
102135
  const deliverableService = new DeliverableService(storage?.deliverableRepo);
101041
102136
  await deliverableService.load();
@@ -101102,17 +102197,28 @@ async function startServer(config, values) {
101102
102197
  await modelCatalog.initialize();
101103
102198
  apiServer.setModelCatalog(modelCatalog);
101104
102199
  if (config.hub?.url) apiServer.setHubUrl(config.hub.url);
102200
+ telemetryService.setStatsProvider(() => {
102201
+ const am = orgService.getAgentManager();
102202
+ return {
102203
+ agentCount: am ? am.listAgents().length : 0,
102204
+ taskCount: taskService.listTasks().length,
102205
+ toolCallCount: billingService.getUsageSummary("default").toolCalls,
102206
+ teamCount: orgService.listTeams("default").length,
102207
+ plan: licenseService.getPlan()
102208
+ };
102209
+ });
102210
+ telemetryService.start();
101105
102211
  const webUiDir = resolveWebUiDir();
101106
102212
  if (webUiDir) {
101107
102213
  apiServer.setWebUiDir(webUiDir);
101108
- log88.info("Web UI static files enabled", { dir: webUiDir });
102214
+ log90.info("Web UI static files enabled", { dir: webUiDir });
101109
102215
  }
101110
102216
  {
101111
102217
  const { LocalFileStorageProvider: LocalFileStorageProvider2 } = await Promise.resolve().then(() => (init_dist6(), dist_exports5));
101112
102218
  const localDir = config.fileStorage?.local?.dir;
101113
102219
  const fileStorage = new LocalFileStorageProvider2(localDir ?? void 0);
101114
102220
  apiServer.setFileStorage(fileStorage);
101115
- log88.info("File storage initialized", { provider: "local", dir: localDir ?? "~/.markus/uploads" });
102221
+ log90.info("File storage initialized", { provider: "local", dir: localDir ?? "~/.markus/uploads" });
101116
102222
  }
101117
102223
  const firstOrgId = "default";
101118
102224
  let ownerUserId = bootstrapOwnerId;
@@ -101133,7 +102239,7 @@ async function startServer(config, values) {
101133
102239
  const builderService = apiServer.getBuilderService();
101134
102240
  if (builderService) {
101135
102241
  const builtinTeamsDir = resolveTemplatesDir("teams");
101136
- if (builtinTeamsDir && existsSync37(builtinTeamsDir)) {
102242
+ if (builtinTeamsDir && existsSync39(builtinTeamsDir)) {
101137
102243
  builderService.setBuiltinTeamTemplatesDir(builtinTeamsDir);
101138
102244
  }
101139
102245
  agentManager.setBuilderService(builderService);
@@ -101196,29 +102302,29 @@ async function startServer(config, values) {
101196
102302
  try {
101197
102303
  storage.chatSessionRepo.migrateLegacyMessages();
101198
102304
  } catch (e) {
101199
- log88.warn("Legacy chat message migration failed", { error: String(e) });
102305
+ log90.warn("Legacy chat message migration failed", { error: String(e) });
101200
102306
  }
101201
102307
  const defaultSessionUserId = ownerUserId;
101202
102308
  try {
101203
102309
  storage.chatSessionRepo.migrateNullUserSessions(defaultSessionUserId);
101204
102310
  } catch (e) {
101205
- log88.warn("NULL user_id session migration failed", { error: String(e) });
102311
+ log90.warn("NULL user_id session migration failed", { error: String(e) });
101206
102312
  }
101207
102313
  try {
101208
102314
  storage.chatSessionRepo.migrateDefaultUserSessions(defaultSessionUserId);
101209
102315
  } catch (e) {
101210
- log88.warn("'default' user_id session migration failed", { error: String(e) });
102316
+ log90.warn("'default' user_id session migration failed", { error: String(e) });
101211
102317
  }
101212
102318
  try {
101213
102319
  storage.notificationRepo.migrateDefaultUserId(defaultSessionUserId);
101214
102320
  } catch (e) {
101215
- log88.warn("'default' user_id notification migration failed", { error: String(e) });
102321
+ log90.warn("'default' user_id notification migration failed", { error: String(e) });
101216
102322
  }
101217
102323
  if (storage.approvalRepo) {
101218
102324
  try {
101219
102325
  storage.approvalRepo.migrateDefaultTargetUserId(defaultSessionUserId);
101220
102326
  } catch (e) {
101221
- log88.warn("'default' target_user_id approval migration failed", { error: String(e) });
102327
+ log90.warn("'default' target_user_id approval migration failed", { error: String(e) });
101222
102328
  }
101223
102329
  }
101224
102330
  for (const info2 of agentManager.listAgents()) {
@@ -101247,7 +102353,7 @@ async function startServer(config, values) {
101247
102353
  isMainSession: true
101248
102354
  }, defaultSessionUserId);
101249
102355
  } catch (e) {
101250
- log88.warn("Failed to persist activity log", { agentId: agentId2, error: String(e) });
102356
+ log90.warn("Failed to persist activity log", { agentId: agentId2, error: String(e) });
101251
102357
  }
101252
102358
  });
101253
102359
  agentManager.getEventBus().on("agent:notify-user", async (evt) => {
@@ -101300,7 +102406,7 @@ ${body}${contextSuffix}`;
101300
102406
  metadata: { agentId: agentId2, agentName: agent.config.name, taskId: taskId2, requirementId: requirementId2, sessionId: mainSession.id }
101301
102407
  });
101302
102408
  } catch (e) {
101303
- log88.warn("Failed to handle notify-user event", { agentId: agentId2, error: String(e) });
102409
+ log90.warn("Failed to handle notify-user event", { agentId: agentId2, error: String(e) });
101304
102410
  }
101305
102411
  });
101306
102412
  agentManager.getEventBus().on("agent:escalation", async (evt) => {
@@ -101342,7 +102448,7 @@ ${reason}`;
101342
102448
  success: false
101343
102449
  });
101344
102450
  } catch (e) {
101345
- log88.warn("Failed to handle escalation event", { agentId: agentId2, error: String(e) });
102451
+ log90.warn("Failed to handle escalation event", { agentId: agentId2, error: String(e) });
101346
102452
  }
101347
102453
  });
101348
102454
  agentManager.getEventBus().on("agent:created", (evt) => {
@@ -101388,7 +102494,7 @@ ${reason}`;
101388
102494
  heartbeatIntervalMs: agent.config.heartbeatIntervalMs
101389
102495
  });
101390
102496
  } catch (err) {
101391
- log88.warn("Failed to persist gateway agent to DB (may already exist)", { error: String(err) });
102497
+ log90.warn("Failed to persist gateway agent to DB (may already exist)", { error: String(err) });
101392
102498
  }
101393
102499
  }
101394
102500
  return { id: agent.id };
@@ -101433,11 +102539,11 @@ ${reason}`;
101433
102539
  }));
101434
102540
  });
101435
102541
  apiServer.setGateway(gateway, gatewaySecret);
101436
- log88.info("External Agent Gateway enabled", { secret: gatewaySecret === "markus-gateway-default-secret-change-me" ? "(default)" : "(custom)" });
102542
+ log90.info("External Agent Gateway enabled", { secret: gatewaySecret === "markus-gateway-default-secret-change-me" ? "(default)" : "(custom)" });
101437
102543
  {
101438
- const hubTokenPath = join32(homedir22(), ".markus", "hub-token");
102544
+ const hubTokenPath = join34(homedir24(), ".markus", "hub-token");
101439
102545
  const createRemoteAgent = async () => {
101440
- const token = existsSync37(hubTokenPath) ? readFileSync26(hubTokenPath, "utf-8").trim() : void 0;
102546
+ const token = existsSync39(hubTokenPath) ? readFileSync28(hubTokenPath, "utf-8").trim() : void 0;
101441
102547
  if (!token) return null;
101442
102548
  const { RemoteAccessAgent: RemoteAccessAgent2 } = await Promise.resolve().then(() => (init_dist8(), dist_exports6));
101443
102549
  return new RemoteAccessAgent2({
@@ -101449,7 +102555,7 @@ ${reason}`;
101449
102555
  });
101450
102556
  };
101451
102557
  apiServer.setRemoteAgentFactory(createRemoteAgent);
101452
- if (config.remote?.enabled !== false) {
102558
+ if (config.remote?.enabled === true) {
101453
102559
  const remoteAgent = await createRemoteAgent();
101454
102560
  if (remoteAgent) {
101455
102561
  apiServer.setRemoteAgent(remoteAgent);
@@ -101457,14 +102563,14 @@ ${reason}`;
101457
102563
  remoteAgent.start().then(() => {
101458
102564
  const status = remoteAgent.getStatus();
101459
102565
  if (status.remoteUrl) {
101460
- log88.info(`Remote access available at ${status.remoteUrl}`);
102566
+ log90.info(`Remote access available at ${status.remoteUrl}`);
101461
102567
  }
101462
102568
  }).catch((err) => {
101463
- log88.warn("Remote access failed to start", { error: String(err) });
102569
+ log90.warn("Remote access failed to start", { error: String(err) });
101464
102570
  });
101465
102571
  }
101466
102572
  } else {
101467
- log88.debug("Remote access: no Hub token yet (can enable later via Settings)");
102573
+ log90.debug("Remote access: no Hub token yet (can enable later via Settings)");
101468
102574
  }
101469
102575
  }
101470
102576
  }
@@ -101475,7 +102581,7 @@ ${reason}`;
101475
102581
  const scheduledTaskRunner = new ScheduledTaskRunner(taskService);
101476
102582
  scheduledTaskRunner.start();
101477
102583
  agentManager.setEscalationHandler((agentId2, reason) => {
101478
- log88.warn("Agent escalation", { agentId: agentId2, reason });
102584
+ log90.warn("Agent escalation", { agentId: agentId2, reason });
101479
102585
  });
101480
102586
  agentManager.setApprovalHandler(async (agentId2, request) => {
101481
102587
  const agents = agentManager.listAgents();
@@ -101542,6 +102648,17 @@ ${reason}`;
101542
102648
  });
101543
102649
  }
101544
102650
  });
102651
+ billingService.setToolCallsTodayProvider(() => {
102652
+ let total = 0;
102653
+ for (const a of agentManager.listAgents()) {
102654
+ try {
102655
+ total += agentManager.getAgent(a.id).getUsageStats().toolCallsToday;
102656
+ } catch {
102657
+ }
102658
+ }
102659
+ return total;
102660
+ });
102661
+ agentManager.setToolCallLimitChecker(() => billingService.checkLimit("default", "tool_call"));
101545
102662
  if (storage) {
101546
102663
  agentManager.setStateChangeHandler(async (agentId2, state) => {
101547
102664
  try {
@@ -101550,7 +102667,7 @@ ${reason}`;
101550
102667
  state.status
101551
102668
  );
101552
102669
  } catch (err) {
101553
- log88.warn("Failed to persist agent state", { agentId: agentId2, error: String(err) });
102670
+ log90.warn("Failed to persist agent state", { agentId: agentId2, error: String(err) });
101554
102671
  }
101555
102672
  apiServer.getWSBroadcaster().broadcastAgentUpdate(agentId2, state.status, {
101556
102673
  lastError: state.lastError,
@@ -101575,7 +102692,7 @@ ${reason}`;
101575
102692
  startedAt: activity.startedAt
101576
102693
  });
101577
102694
  } catch (err) {
101578
- log88.warn("Failed to persist activity start", { activityId: activity.id, error: String(err) });
102695
+ log90.warn("Failed to persist activity start", { activityId: activity.id, error: String(err) });
101579
102696
  }
101580
102697
  },
101581
102698
  onLog: (data) => {
@@ -101591,20 +102708,20 @@ ${reason}`;
101591
102708
  metadata: data.metadata
101592
102709
  });
101593
102710
  } catch (err) {
101594
- log88.warn("Failed to persist execution stream activity log", { activityId: data.activityId, error: String(err) });
102711
+ log90.warn("Failed to persist execution stream activity log", { activityId: data.activityId, error: String(err) });
101595
102712
  }
101596
102713
  }
101597
102714
  try {
101598
102715
  actRepo.insertActivityLog(data);
101599
102716
  } catch (err) {
101600
- log88.warn("Failed to persist activity log", { activityId: data.activityId, error: String(err) });
102717
+ log90.warn("Failed to persist activity log", { activityId: data.activityId, error: String(err) });
101601
102718
  }
101602
102719
  },
101603
102720
  onEnd: (activityId, summary) => {
101604
102721
  try {
101605
102722
  actRepo.updateActivity(activityId, summary);
101606
102723
  } catch (err) {
101607
- log88.warn("Failed to persist activity end", { activityId, error: String(err) });
102724
+ log90.warn("Failed to persist activity end", { activityId, error: String(err) });
101608
102725
  }
101609
102726
  }
101610
102727
  });
@@ -101647,14 +102764,14 @@ ${reason}`;
101647
102764
  queuedAt: item.queuedAt
101648
102765
  });
101649
102766
  } catch (e) {
101650
- log88.warn("Failed to persist mailbox item", { id: item.id, error: String(e) });
102767
+ log90.warn("Failed to persist mailbox item", { id: item.id, error: String(e) });
101651
102768
  }
101652
102769
  },
101653
102770
  updateStatus: (itemId, status, extra) => {
101654
102771
  try {
101655
102772
  mbRepo.updateStatus(itemId, status, extra);
101656
102773
  } catch (e) {
101657
- log88.warn("Failed to update mailbox status", { itemId, error: String(e) });
102774
+ log90.warn("Failed to update mailbox status", { itemId, error: String(e) });
101658
102775
  }
101659
102776
  },
101660
102777
  markStaleProcessingAsDropped: (aid) => mbRepo.markStaleProcessingAsDropped(aid),
@@ -101697,7 +102814,7 @@ ${reason}`;
101697
102814
  }
101698
102815
  });
101699
102816
  const { dropped, restored, expired, merged } = mailbox.recoverStaleItems();
101700
- if (dropped > 0 || restored > 0 || expired > 0 || merged > 0) log88.info("Mailbox recovery on startup", { agentId: agentId2, dropped, restored, expired, merged });
102817
+ if (dropped > 0 || restored > 0 || expired > 0 || merged > 0) log90.info("Mailbox recovery on startup", { agentId: agentId2, dropped, restored, expired, merged });
101701
102818
  agent.getAttentionController().setDecisionPersistence({
101702
102819
  save: (decision) => {
101703
102820
  try {
@@ -101712,7 +102829,7 @@ ${reason}`;
101712
102829
  createdAt: decision.createdAt
101713
102830
  });
101714
102831
  } catch (e) {
101715
- log88.warn("Failed to persist decision", { id: decision.id, error: String(e) });
102832
+ log90.warn("Failed to persist decision", { id: decision.id, error: String(e) });
101716
102833
  }
101717
102834
  }
101718
102835
  });
@@ -101807,7 +102924,7 @@ ${reason}`;
101807
102924
  llmConfig: agent.config.llmConfig,
101808
102925
  heartbeatIntervalMs: agent.config.heartbeatIntervalMs
101809
102926
  }).catch((err) => {
101810
- log88.warn("Failed to persist runtime-created agent to DB", { agentId: agentId2, error: String(err) });
102927
+ log90.warn("Failed to persist runtime-created agent to DB", { agentId: agentId2, error: String(err) });
101811
102928
  });
101812
102929
  } catch {
101813
102930
  }
@@ -101890,7 +103007,7 @@ ${reason}`;
101890
103007
  const nextMidnight = new Date(now3.getFullYear(), now3.getMonth(), now3.getDate() + 1, 0, 0, 0, 0);
101891
103008
  const msUntilMidnight = nextMidnight.getTime() - now3.getTime();
101892
103009
  setTimeout(() => {
101893
- log88.info("Daily token reset triggered");
103010
+ log90.info("Daily token reset triggered");
101894
103011
  for (const agentInfo of agentManager.listAgents()) {
101895
103012
  try {
101896
103013
  const agent = agentManager.getAgent(agentInfo.id);
@@ -101900,7 +103017,7 @@ ${reason}`;
101900
103017
  }
101901
103018
  scheduleDailyReset();
101902
103019
  }, msUntilMidnight);
101903
- log88.info(`Daily token reset scheduled in ${Math.round(msUntilMidnight / 6e4)} minutes`);
103020
+ log90.info(`Daily token reset scheduled in ${Math.round(msUntilMidnight / 6e4)} minutes`);
101904
103021
  };
101905
103022
  scheduleDailyReset();
101906
103023
  const messageRouter = new MessageRouter();
@@ -101931,7 +103048,7 @@ ${reason}`;
101931
103048
  durationMs: Date.now() - startTs,
101932
103049
  success: false
101933
103050
  });
101934
- log88.error("Agent message handler error", { error: String(error) });
103051
+ log90.error("Agent message handler error", { error: String(error) });
101935
103052
  return void 0;
101936
103053
  }
101937
103054
  });
@@ -101967,7 +103084,7 @@ ${reason}`;
101967
103084
  if (info2.updateAvailable) {
101968
103085
  console.log(`
101969
103086
  \x1B[33m\u2B06 New version available: v${info2.latestVersion} (current: v${info2.currentVersion})\x1B[0m`);
101970
- console.log(` Run \x1B[1mnpm i -g @markus-global/cli\x1B[0m to upgrade
103087
+ console.log(` Visit \x1B[1mhttps://markus.global/download\x1B[0m to download the latest version
101971
103088
  `);
101972
103089
  }
101973
103090
  }).catch(() => {
@@ -101976,7 +103093,7 @@ ${reason}`;
101976
103093
  try {
101977
103094
  await taskService.resumeInProgressTasks();
101978
103095
  } catch (err) {
101979
- log88.warn("Failed to auto-resume in_progress tasks", { error: String(err) });
103096
+ log90.warn("Failed to auto-resume in_progress tasks", { error: String(err) });
101980
103097
  }
101981
103098
  });
101982
103099
  process.on("SIGINT", () => {
@@ -101992,7 +103109,7 @@ ${reason}`;
101992
103109
  await new Promise(() => {
101993
103110
  });
101994
103111
  }
101995
- var log88;
103112
+ var log90;
101996
103113
  var init_start = __esm({
101997
103114
  "src/commands/start.ts"() {
101998
103115
  "use strict";
@@ -102004,7 +103121,7 @@ var init_start = __esm({
102004
103121
  init_logger2();
102005
103122
  init_browser();
102006
103123
  init_startupProgress();
102007
- log88 = createLogger("cli");
103124
+ log90 = createLogger("cli");
102008
103125
  }
102009
103126
  });
102010
103127
 
@@ -102633,8 +103750,8 @@ ${C3.BOLD}${C3.CYAN}\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
102633
103750
  }
102634
103751
  }
102635
103752
  section("Storage");
102636
- const { homedir: homedir23 } = await import("node:os");
102637
- const storageDir = `${homedir23()}/.markus`;
103753
+ const { homedir: homedir25 } = await import("node:os");
103754
+ const storageDir = `${homedir25()}/.markus`;
102638
103755
  const dataFile = `${storageDir}/data.db`;
102639
103756
  try {
102640
103757
  if (!fs.existsSync(storageDir)) {
@@ -102658,7 +103775,7 @@ ${C3.BOLD}${C3.CYAN}\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
102658
103775
  checkFail(`Storage check failed: ${e}`);
102659
103776
  }
102660
103777
  section("Skills");
102661
- const skillsDir = `${homedir23()}/.markus/skills`;
103778
+ const skillsDir = `${homedir25()}/.markus/skills`;
102662
103779
  if (fs.existsSync(skillsDir)) {
102663
103780
  try {
102664
103781
  const entries2 = fs.readdirSync(skillsDir);
@@ -102737,9 +103854,9 @@ ${C3.BOLD}\u25C6 Summary${C3.RESET}
102737
103854
  }
102738
103855
  }
102739
103856
  async function getDefaultConfigPath2() {
102740
- const { homedir: homedir23 } = await import("node:os");
102741
- const { join: join33 } = await import("node:path");
102742
- return join33(homedir23(), ".markus", "markus.json");
103857
+ const { homedir: homedir25 } = await import("node:os");
103858
+ const { join: join35 } = await import("node:path");
103859
+ return join35(homedir25(), ".markus", "markus.json");
102743
103860
  }
102744
103861
  function registerDoctorCommand(program2) {
102745
103862
  program2.command("doctor").description("Diagnose Markus configuration issues and environment health").option("--fix", "Attempt to automatically fix issues").option("--verbose", "Show detailed output").action(async (opts) => {
@@ -103241,19 +104358,19 @@ __export(install_agent_exports, {
103241
104358
  import { execSync as execSync5 } from "node:child_process";
103242
104359
  import { randomBytes as randomBytes6 } from "node:crypto";
103243
104360
  function registerInstallAgentCommands(program2) {
103244
- program2.command("install <platform>").description("Install an external agent platform and connect it to Markus").option("--org-id <id>", "Organization ID", "default").option("--agent-name <name>", "Agent display name").option("--skip-install", "Skip npm install (platform already installed)").option("--skip-init", "Skip platform initialization").option("--skip-connect", "Only install, do not register with Markus").action(async (platform4, opts, cmd) => {
104361
+ program2.command("install <platform>").description("Install an external agent platform and connect it to Markus").option("--org-id <id>", "Organization ID", "default").option("--agent-name <name>", "Agent display name").option("--skip-install", "Skip npm install (platform already installed)").option("--skip-init", "Skip platform initialization").option("--skip-connect", "Only install, do not register with Markus").action(async (platform5, opts, cmd) => {
103245
104362
  const g = cmd.optsWithGlobals();
103246
- const connector = findConnector(platform4);
104363
+ const connector = findConnector(platform5);
103247
104364
  if (!connector) {
103248
104365
  const available = loadConnectors().map((c) => c.platform).join(", ");
103249
- fail(`Unknown platform "${platform4}". Available: ${available || "none"}`);
104366
+ fail(`Unknown platform "${platform5}". Available: ${available || "none"}`);
103250
104367
  return;
103251
104368
  }
103252
104369
  console.log(`
103253
104370
  Installing ${connector.displayName}...
103254
104371
  `);
103255
104372
  const scan = scanInstalledPlatforms();
103256
- const existing = scan.find((s2) => s2.platform === platform4);
104373
+ const existing = scan.find((s2) => s2.platform === platform5);
103257
104374
  const alreadyInstalled = existing?.installed;
103258
104375
  if (alreadyInstalled && !opts.skipInstall) {
103259
104376
  console.log(` [1/5] ${connector.displayName} is already installed.`);
@@ -103289,13 +104406,13 @@ function registerInstallAgentCommands(program2) {
103289
104406
  console.log(` [4/5] Token generation skipped.`);
103290
104407
  console.log(` [5/5] Config write skipped.`);
103291
104408
  console.log(`
103292
- ${connector.displayName} installed. Run \`markus install ${platform4}\` again without --skip-connect to connect later.
104409
+ ${connector.displayName} installed. Run \`markus install ${platform5}\` again without --skip-connect to connect later.
103293
104410
  `);
103294
104411
  return;
103295
104412
  }
103296
104413
  const client = createClient(g);
103297
104414
  const serverUrl = g.server || process.env["MARKUS_API_URL"] || "http://localhost:8056";
103298
- const agentId2 = `${platform4}-${randomBytes6(4).toString("hex")}`;
104415
+ const agentId2 = `${platform5}-${randomBytes6(4).toString("hex")}`;
103299
104416
  const agentName = opts.agentName || connector.defaultAgentName || `${connector.displayName} Agent`;
103300
104417
  const capabilities = connector.defaultCapabilities ?? [];
103301
104418
  try {
@@ -103358,7 +104475,7 @@ function registerInstallAgentCommands(program2) {
103358
104475
  Connection failed: ${e.message}`);
103359
104476
  console.log(` ${connector.displayName} was installed but could not connect to Markus.`);
103360
104477
  console.log(` Make sure the Markus server is running (\`markus start\`), then run:`);
103361
- console.log(` markus install ${platform4}
104478
+ console.log(` markus install ${platform5}
103362
104479
  `);
103363
104480
  return;
103364
104481
  }
@@ -103454,14 +104571,14 @@ __export(system_exports, {
103454
104571
  registerSystemCommands: () => registerSystemCommands
103455
104572
  });
103456
104573
  import { execSync as execSync6 } from "node:child_process";
103457
- import { existsSync as existsSync38, readFileSync as readFileSync27 } from "node:fs";
103458
- import { resolve as resolve19, dirname as dirname12 } from "node:path";
104574
+ import { existsSync as existsSync40, readFileSync as readFileSync29 } from "node:fs";
104575
+ import { resolve as resolve19, dirname as dirname14 } from "node:path";
103459
104576
  import { fileURLToPath as fileURLToPath7 } from "node:url";
103460
104577
  function findMarkusRoot() {
103461
- let dir = dirname12(fileURLToPath7(import.meta.url));
104578
+ let dir = dirname14(fileURLToPath7(import.meta.url));
103462
104579
  for (let i = 0; i < 10; i++) {
103463
- if (existsSync38(resolve19(dir, "package.json")) && existsSync38(resolve19(dir, "packages"))) return dir;
103464
- dir = dirname12(dir);
104580
+ if (existsSync40(resolve19(dir, "package.json")) && existsSync40(resolve19(dir, "packages"))) return dir;
104581
+ dir = dirname14(dir);
103465
104582
  }
103466
104583
  return null;
103467
104584
  }
@@ -103511,13 +104628,13 @@ function registerSystemCommands(program2) {
103511
104628
  if (markusRoot) {
103512
104629
  if (!info2.currentVersion) {
103513
104630
  try {
103514
- const pkg = JSON.parse(readFileSync27(resolve19(markusRoot, "package.json"), "utf-8"));
104631
+ const pkg = JSON.parse(readFileSync29(resolve19(markusRoot, "package.json"), "utf-8"));
103515
104632
  info2.currentVersion = pkg.version;
103516
104633
  } catch {
103517
104634
  }
103518
104635
  }
103519
104636
  try {
103520
- const isGit = existsSync38(resolve19(markusRoot, ".git"));
104637
+ const isGit = existsSync40(resolve19(markusRoot, ".git"));
103521
104638
  if (isGit) {
103522
104639
  info2.gitBranch = execSync6("git rev-parse --abbrev-ref HEAD", { cwd: markusRoot, encoding: "utf-8" }).trim();
103523
104640
  info2.gitCommit = execSync6("git rev-parse --short HEAD", { cwd: markusRoot, encoding: "utf-8" }).trim();
@@ -103556,7 +104673,7 @@ function registerSystemCommands(program2) {
103556
104673
  fail("Cannot locate Markus installation directory");
103557
104674
  return;
103558
104675
  }
103559
- if (!existsSync38(resolve19(markusRoot, ".git"))) {
104676
+ if (!existsSync40(resolve19(markusRoot, ".git"))) {
103560
104677
  fail("Markus installation is not a git repository. Update manually.");
103561
104678
  return;
103562
104679
  }
@@ -103591,7 +104708,7 @@ var init_system = __esm({
103591
104708
 
103592
104709
  // src/index.ts
103593
104710
  import { resolve as resolve20 } from "node:path";
103594
- import { readFileSync as readFileSync28, existsSync as existsSync39 } from "node:fs";
104711
+ import { readFileSync as readFileSync30, existsSync as existsSync41 } from "node:fs";
103595
104712
  import process2 from "node:process";
103596
104713
 
103597
104714
  // ../../node_modules/.pnpm/commander@14.0.3/node_modules/commander/esm.mjs
@@ -103615,8 +104732,8 @@ var {
103615
104732
  init_dist();
103616
104733
  init_output();
103617
104734
  var envPath = resolve20(process2.cwd(), ".env");
103618
- if (existsSync39(envPath)) {
103619
- for (const line of readFileSync28(envPath, "utf-8").split("\n")) {
104735
+ if (existsSync41(envPath)) {
104736
+ for (const line of readFileSync30(envPath, "utf-8").split("\n")) {
103620
104737
  const trimmed = line.trim();
103621
104738
  if (!trimmed || trimmed.startsWith("#")) continue;
103622
104739
  const eqIdx = trimmed.indexOf("=");