codeharbor 0.1.15 → 0.1.16

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/.env.example CHANGED
@@ -38,6 +38,7 @@ MATRIX_TYPING_TIMEOUT_MS=10000
38
38
  SESSION_ACTIVE_WINDOW_MINUTES=20
39
39
 
40
40
  # Group trigger defaults.
41
+ GROUP_DIRECT_MODE_ENABLED=false
41
42
  GROUP_TRIGGER_ALLOW_MENTION=true
42
43
  GROUP_TRIGGER_ALLOW_REPLY=true
43
44
  GROUP_TRIGGER_ALLOW_ACTIVE_WINDOW=true
@@ -78,6 +79,8 @@ ADMIN_TOKEN=
78
79
  # Each item: {"token":"...","role":"admin|viewer","actor":"ops-name"}
79
80
  # Example:
80
81
  # ADMIN_TOKENS_JSON=[{"token":"admin-secret","role":"admin","actor":"ops-admin"},{"token":"viewer-secret","role":"viewer","actor":"ops-audit"}]
82
+ # Rotate helper:
83
+ # ./scripts/rotate-admin-token.sh --target rbac --role admin --actor ops-admin
81
84
  ADMIN_TOKENS_JSON=
82
85
  # Optional IP allowlist (comma-separated, for example: 127.0.0.1,192.168.1.10).
83
86
  ADMIN_IP_ALLOWLIST=
package/README.md CHANGED
@@ -327,6 +327,7 @@ Open these UI routes in browser:
327
327
 
328
328
  Main endpoints:
329
329
 
330
+ - `GET /api/admin/auth/status`
330
331
  - `GET /api/admin/config/global`
331
332
  - `PUT /api/admin/config/global`
332
333
  - `GET /api/admin/config/rooms`
@@ -354,6 +355,14 @@ RBAC behavior:
354
355
  - `viewer` tokens can call read endpoints (`GET /api/admin/*`)
355
356
  - `admin` tokens can call read + write endpoints (`PUT/POST/DELETE /api/admin/*`)
356
357
  - for `ADMIN_TOKENS_JSON`, audit actor is derived from token identity (`actor` field), not `x-admin-actor`
358
+ - Admin UI shows current permission status (role/source) after saving auth
359
+
360
+ Rotate tokens quickly (repository script):
361
+
362
+ ```bash
363
+ ./scripts/rotate-admin-token.sh --target rbac --role admin --actor ops-admin
364
+ ./scripts/rotate-admin-token.sh --target rbac --role viewer --actor ops-audit
365
+ ```
357
366
 
358
367
  Note: `PUT /api/admin/config/global` writes to `.env` and marks changes as restart-required.
359
368
 
@@ -393,12 +402,14 @@ If any check fails, it prints actionable fix commands (for example `codeharbor i
393
402
  - Direct Message (DM)
394
403
  - all text messages are processed by default (no prefix required)
395
404
  - Group Room
405
+ - when `GROUP_DIRECT_MODE_ENABLED=true`, all non-empty messages are processed directly (no prefix/mention/reply required)
396
406
  - processed when **any allowed trigger** matches:
397
407
  - message mentions bot user id
398
408
  - message replies to a bot message
399
409
  - sender has an active conversation window
400
410
  - optional explicit prefix match (`MATRIX_COMMAND_PREFIX`)
401
411
  - Trigger Policy
412
+ - `GROUP_DIRECT_MODE_ENABLED` controls whether groups bypass trigger matching entirely
402
413
  - global defaults via `GROUP_TRIGGER_ALLOW_*`
403
414
  - per-room overrides via `ROOM_TRIGGER_POLICY_JSON`
404
415
  - Active Conversation Window
package/dist/cli.js CHANGED
@@ -771,6 +771,19 @@ var AdminServer = class {
771
771
  });
772
772
  return;
773
773
  }
774
+ if (req.method === "GET" && url.pathname === "/api/admin/auth/status") {
775
+ this.sendJson(res, 200, {
776
+ ok: true,
777
+ data: {
778
+ authenticated: Boolean(authIdentity),
779
+ role: authIdentity?.role ?? null,
780
+ source: authIdentity?.source ?? "none",
781
+ actor: resolveIdentityActor(authIdentity),
782
+ canWrite: authIdentity ? hasRequiredAdminRole(authIdentity.role, "admin") : false
783
+ }
784
+ });
785
+ return;
786
+ }
774
787
  if (req.method === "GET" && url.pathname === "/api/admin/config/global") {
775
788
  this.sendJson(res, 200, {
776
789
  ok: true,
@@ -1010,6 +1023,12 @@ var AdminServer = class {
1010
1023
  envUpdates.SESSION_ACTIVE_WINDOW_MINUTES = String(value);
1011
1024
  updatedKeys.push("sessionActiveWindowMinutes");
1012
1025
  }
1026
+ if ("groupDirectModeEnabled" in body) {
1027
+ const value = normalizeBoolean(body.groupDirectModeEnabled, this.config.groupDirectModeEnabled);
1028
+ this.config.groupDirectModeEnabled = value;
1029
+ envUpdates.GROUP_DIRECT_MODE_ENABLED = String(value);
1030
+ updatedKeys.push("groupDirectModeEnabled");
1031
+ }
1013
1032
  if ("cliCompat" in body) {
1014
1033
  const compat = asObject(body.cliCompat, "cliCompat");
1015
1034
  if ("enabled" in compat) {
@@ -1205,6 +1224,7 @@ function buildGlobalConfigSnapshot(config) {
1205
1224
  matrixCommandPrefix: config.matrixCommandPrefix,
1206
1225
  codexWorkdir: config.codexWorkdir,
1207
1226
  rateLimiter: { ...config.rateLimiter },
1227
+ groupDirectModeEnabled: config.groupDirectModeEnabled,
1208
1228
  defaultGroupTriggerPolicy: { ...config.defaultGroupTriggerPolicy },
1209
1229
  matrixProgressUpdates: config.matrixProgressUpdates,
1210
1230
  matrixProgressMinIntervalMs: config.matrixProgressMinIntervalMs,
@@ -1434,12 +1454,19 @@ function readAdminToken(req) {
1434
1454
  const fromHeader = normalizeHeaderValue(req.headers["x-admin-token"]);
1435
1455
  return fromHeader || null;
1436
1456
  }
1457
+ function resolveIdentityActor(identity) {
1458
+ if (!identity || identity.source !== "scoped") {
1459
+ return null;
1460
+ }
1461
+ if (identity.actor) {
1462
+ return identity.actor;
1463
+ }
1464
+ return identity.role === "admin" ? "admin-token" : "viewer-token";
1465
+ }
1437
1466
  function resolveAuditActor(req, identity) {
1438
- if (identity?.source === "scoped") {
1439
- if (identity.actor) {
1440
- return identity.actor;
1441
- }
1442
- return identity.role === "admin" ? "admin-token" : "viewer-token";
1467
+ const scopedActor = resolveIdentityActor(identity);
1468
+ if (scopedActor) {
1469
+ return scopedActor;
1443
1470
  }
1444
1471
  const actor = normalizeHeaderValue(req.headers["x-admin-actor"]);
1445
1472
  return actor || null;
@@ -1717,6 +1744,7 @@ var ADMIN_CONSOLE_HTML = `<!doctype html>
1717
1744
  <button id="auth-clear-btn" type="button" class="secondary">Clear Auth</button>
1718
1745
  </div>
1719
1746
  <div id="notice" class="notice">Ready.</div>
1747
+ <p id="auth-role" class="muted">Permission: unknown</p>
1720
1748
  </section>
1721
1749
 
1722
1750
  <section class="panel" data-view="settings-global">
@@ -1772,6 +1800,7 @@ var ADMIN_CONSOLE_HTML = `<!doctype html>
1772
1800
  <input id="global-concurrency-room" type="number" min="0" />
1773
1801
  </label>
1774
1802
 
1803
+ <label class="checkbox"><input id="global-direct-mode" type="checkbox" /><span>Group direct mode (no trigger required)</span></label>
1775
1804
  <label class="checkbox"><input id="global-trigger-mention" type="checkbox" /><span>Trigger: mention</span></label>
1776
1805
  <label class="checkbox"><input id="global-trigger-reply" type="checkbox" /><span>Trigger: reply</span></label>
1777
1806
  <label class="checkbox"><input id="global-trigger-window" type="checkbox" /><span>Trigger: active window</span></label>
@@ -1916,6 +1945,7 @@ var ADMIN_CONSOLE_HTML = `<!doctype html>
1916
1945
  var tokenInput = document.getElementById("auth-token");
1917
1946
  var actorInput = document.getElementById("auth-actor");
1918
1947
  var noticeNode = document.getElementById("notice");
1948
+ var authRoleNode = document.getElementById("auth-role");
1919
1949
  var roomListBody = document.getElementById("room-list-body");
1920
1950
  var healthBody = document.getElementById("health-body");
1921
1951
  var auditBody = document.getElementById("audit-body");
@@ -1927,6 +1957,7 @@ var ADMIN_CONSOLE_HTML = `<!doctype html>
1927
1957
  localStorage.setItem(storageTokenKey, tokenInput.value.trim());
1928
1958
  localStorage.setItem(storageActorKey, actorInput.value.trim());
1929
1959
  showNotice("ok", "Auth settings saved to localStorage.");
1960
+ void refreshAuthStatus();
1930
1961
  });
1931
1962
 
1932
1963
  document.getElementById("auth-clear-btn").addEventListener("click", function () {
@@ -1935,6 +1966,7 @@ var ADMIN_CONSOLE_HTML = `<!doctype html>
1935
1966
  localStorage.removeItem(storageTokenKey);
1936
1967
  localStorage.removeItem(storageActorKey);
1937
1968
  showNotice("warn", "Auth settings cleared.");
1969
+ void refreshAuthStatus();
1938
1970
  });
1939
1971
 
1940
1972
  document.getElementById("global-save-btn").addEventListener("click", saveGlobal);
@@ -1959,6 +1991,7 @@ var ADMIN_CONSOLE_HTML = `<!doctype html>
1959
1991
  } else {
1960
1992
  handleRoute();
1961
1993
  }
1994
+ void refreshAuthStatus();
1962
1995
 
1963
1996
  function getCurrentView() {
1964
1997
  return routeToView[window.location.hash] || "settings-global";
@@ -2049,6 +2082,29 @@ var ADMIN_CONSOLE_HTML = `<!doctype html>
2049
2082
  noticeNode.textContent = message;
2050
2083
  }
2051
2084
 
2085
+ async function refreshAuthStatus() {
2086
+ try {
2087
+ var response = await apiRequest("/api/admin/auth/status", "GET");
2088
+ var data = response.data || {};
2089
+ if (!data.role) {
2090
+ authRoleNode.textContent = "Permission: unauthenticated";
2091
+ return;
2092
+ }
2093
+
2094
+ var role = String(data.role).toUpperCase();
2095
+ var source = data.source ? " (" + String(data.source) + ")" : "";
2096
+ var actor = data.actor ? " as " + String(data.actor) : "";
2097
+ authRoleNode.textContent = "Permission: " + role + source + actor;
2098
+ } catch (error) {
2099
+ var message = error && error.message ? String(error.message) : "";
2100
+ if (/Unauthorized/i.test(message)) {
2101
+ authRoleNode.textContent = "Permission: unauthenticated";
2102
+ return;
2103
+ }
2104
+ authRoleNode.textContent = "Permission: unknown";
2105
+ }
2106
+ }
2107
+
2052
2108
  function renderEmptyRow(body, columns, text) {
2053
2109
  body.innerHTML = "";
2054
2110
  var row = document.createElement("tr");
@@ -2080,6 +2136,7 @@ var ADMIN_CONSOLE_HTML = `<!doctype html>
2080
2136
  document.getElementById("global-concurrency-global").value = String(rateLimiter.maxConcurrentGlobal || 0);
2081
2137
  document.getElementById("global-concurrency-user").value = String(rateLimiter.maxConcurrentPerUser || 0);
2082
2138
  document.getElementById("global-concurrency-room").value = String(rateLimiter.maxConcurrentPerRoom || 0);
2139
+ document.getElementById("global-direct-mode").checked = Boolean(data.groupDirectModeEnabled);
2083
2140
 
2084
2141
  document.getElementById("global-trigger-mention").checked = Boolean(trigger.allowMention);
2085
2142
  document.getElementById("global-trigger-reply").checked = Boolean(trigger.allowReply);
@@ -2112,6 +2169,7 @@ var ADMIN_CONSOLE_HTML = `<!doctype html>
2112
2169
  matrixProgressMinIntervalMs: asNumber("global-progress-interval", 2500),
2113
2170
  matrixTypingTimeoutMs: asNumber("global-typing-timeout", 10000),
2114
2171
  sessionActiveWindowMinutes: asNumber("global-active-window", 20),
2172
+ groupDirectModeEnabled: asBool("global-direct-mode"),
2115
2173
  rateLimiter: {
2116
2174
  windowMs: asNumber("global-rate-window", 60000),
2117
2175
  maxRequestsPerUser: asNumber("global-rate-user", 20),
@@ -2535,6 +2593,7 @@ function findBreakIndex(candidate) {
2535
2593
  }
2536
2594
 
2537
2595
  // src/channels/matrix-channel.ts
2596
+ var LOCAL_TXN_PREFIX = "codeharbor-";
2538
2597
  var MatrixChannel = class {
2539
2598
  config;
2540
2599
  logger;
@@ -2575,7 +2634,7 @@ var MatrixChannel = class {
2575
2634
  }
2576
2635
  const chunks = this.splitReplies ? splitText(text, this.chunkSize) : [text];
2577
2636
  for (const chunk of chunks) {
2578
- await this.client.sendTextMessage(conversationId, chunk);
2637
+ await this.sendRichText(conversationId, chunk, "m.text");
2579
2638
  }
2580
2639
  }
2581
2640
  async sendNotice(conversationId, text) {
@@ -2584,7 +2643,7 @@ var MatrixChannel = class {
2584
2643
  }
2585
2644
  const chunks = this.splitReplies ? splitText(text, this.chunkSize) : [text];
2586
2645
  for (const chunk of chunks) {
2587
- await this.client.sendNotice(conversationId, chunk);
2646
+ await this.sendRichText(conversationId, chunk, "m.notice");
2588
2647
  }
2589
2648
  }
2590
2649
  async upsertProgressNotice(conversationId, text, replaceEventId) {
@@ -2596,7 +2655,10 @@ var MatrixChannel = class {
2596
2655
  throw new Error("Progress notice cannot be empty.");
2597
2656
  }
2598
2657
  if (!replaceEventId) {
2599
- const response2 = await this.client.sendNotice(conversationId, normalized);
2658
+ const response2 = await this.sendRawEvent(
2659
+ conversationId,
2660
+ buildMatrixRichMessageContent(normalized, "m.notice")
2661
+ );
2600
2662
  return response2.event_id;
2601
2663
  }
2602
2664
  const content = {
@@ -2611,8 +2673,7 @@ var MatrixChannel = class {
2611
2673
  event_id: replaceEventId
2612
2674
  }
2613
2675
  };
2614
- const sendEditEvent = this.client.sendEvent;
2615
- const response = await sendEditEvent(conversationId, import_matrix_js_sdk.EventType.RoomMessage, content);
2676
+ const response = await this.sendRawEvent(conversationId, content);
2616
2677
  return response.event_id;
2617
2678
  }
2618
2679
  async setTyping(conversationId, isTyping, timeoutMs) {
@@ -2648,7 +2709,10 @@ var MatrixChannel = class {
2648
2709
  return;
2649
2710
  }
2650
2711
  const senderId = event.getSender();
2651
- if (!senderId || senderId === this.config.matrixUserId) {
2712
+ if (!senderId) {
2713
+ return;
2714
+ }
2715
+ if (senderId === this.config.matrixUserId && isLikelyLocalEcho(event)) {
2652
2716
  return;
2653
2717
  }
2654
2718
  const content = event.getContent();
@@ -2752,6 +2816,32 @@ var MatrixChannel = class {
2752
2816
  await this.joinInvitedRoom(room.roomId);
2753
2817
  }
2754
2818
  }
2819
+ async sendRichText(conversationId, text, msgtype) {
2820
+ const payload = buildMatrixRichMessageContent(text, msgtype);
2821
+ await this.sendRawEvent(conversationId, payload);
2822
+ }
2823
+ async sendRawEvent(conversationId, content) {
2824
+ const txnId = `${LOCAL_TXN_PREFIX}${Date.now()}-${Math.floor(Math.random() * 1e6)}`;
2825
+ const response = await fetch(
2826
+ `${this.config.matrixHomeserver}/_matrix/client/v3/rooms/${encodeURIComponent(conversationId)}/send/m.room.message/${encodeURIComponent(txnId)}`,
2827
+ {
2828
+ method: "PUT",
2829
+ headers: {
2830
+ Authorization: `Bearer ${this.config.matrixAccessToken}`,
2831
+ "Content-Type": "application/json"
2832
+ },
2833
+ body: JSON.stringify(content)
2834
+ }
2835
+ );
2836
+ if (!response.ok) {
2837
+ throw new Error(`Matrix send failed (${response.status} ${response.statusText})`);
2838
+ }
2839
+ const payload = await response.json();
2840
+ if (!payload.event_id || typeof payload.event_id !== "string") {
2841
+ throw new Error("Matrix send failed (missing event_id)");
2842
+ }
2843
+ return { event_id: payload.event_id };
2844
+ }
2755
2845
  async hydrateAttachments(attachments, eventId) {
2756
2846
  if (!this.fetchMedia || attachments.length === 0) {
2757
2847
  return attachments;
@@ -2822,6 +2912,17 @@ function buildRequestId(eventId) {
2822
2912
  const suffix = Math.random().toString(36).slice(2, 8);
2823
2913
  return `${eventId}:${suffix}`;
2824
2914
  }
2915
+ function isLikelyLocalEcho(event) {
2916
+ const unsigned = event.getUnsigned();
2917
+ if (!unsigned || typeof unsigned !== "object") {
2918
+ return false;
2919
+ }
2920
+ const transactionId = unsigned.transaction_id;
2921
+ if (typeof transactionId !== "string" || !transactionId) {
2922
+ return false;
2923
+ }
2924
+ return transactionId.startsWith(LOCAL_TXN_PREFIX);
2925
+ }
2825
2926
  function isDirectRoom(room) {
2826
2927
  return room.getJoinedMemberCount() <= 2;
2827
2928
  }
@@ -2913,6 +3014,61 @@ function resolveFileExtension(fileName, mimeType) {
2913
3014
  }
2914
3015
  return ".bin";
2915
3016
  }
3017
+ function buildMatrixRichMessageContent(body, msgtype) {
3018
+ return {
3019
+ msgtype,
3020
+ body,
3021
+ format: "org.matrix.custom.html",
3022
+ formatted_body: renderMatrixHtml(body, msgtype)
3023
+ };
3024
+ }
3025
+ function renderMatrixHtml(body, msgtype) {
3026
+ const normalized = body.replace(/\r\n/g, "\n");
3027
+ const sections = [];
3028
+ const codeFencePattern = /```([^\n`]*)\n?([\s\S]*?)```/g;
3029
+ let cursor = 0;
3030
+ let match;
3031
+ while ((match = codeFencePattern.exec(normalized)) !== null) {
3032
+ const before = normalized.slice(cursor, match.index);
3033
+ const renderedBefore = renderTextSection(before);
3034
+ if (renderedBefore) {
3035
+ sections.push(renderedBefore);
3036
+ }
3037
+ const language = escapeHtml(match[1]?.trim() || "text");
3038
+ const code = escapeHtml(match[2].replace(/\n$/, ""));
3039
+ const label = language && language !== "text" ? `\u4EE3\u7801 (${language})` : "\u4EE3\u7801";
3040
+ sections.push(
3041
+ `<p><font color="#3558d1"><b>${label}</b></font></p><pre><code>${code}</code></pre>`
3042
+ );
3043
+ cursor = match.index + match[0].length;
3044
+ }
3045
+ const tail = normalized.slice(cursor);
3046
+ const renderedTail = renderTextSection(tail);
3047
+ if (renderedTail) {
3048
+ sections.push(renderedTail);
3049
+ }
3050
+ if (sections.length === 0) {
3051
+ sections.push("<p>(\u7A7A\u6D88\u606F)</p>");
3052
+ }
3053
+ const badge = msgtype === "m.notice" ? `<p><font color="#8a5a00"><b>\u{1F4E3} CodeHarbor \u63D0\u793A</b></font></p>` : `<p><font color="#1f7a5a"><b>\u{1F916} AI \u56DE\u590D</b></font></p>`;
3054
+ return `<div>${badge}${sections.join("")}</div>`;
3055
+ }
3056
+ function renderTextSection(raw) {
3057
+ if (!raw.trim()) {
3058
+ return "";
3059
+ }
3060
+ const normalized = raw.replace(/\r\n/g, "\n").trim();
3061
+ const paragraphs = normalized.split(/\n{2,}/);
3062
+ const rendered = paragraphs.map((paragraph) => {
3063
+ const escaped = escapeHtml(paragraph);
3064
+ const inlineCode = escaped.replace(/`([^`\n]+)`/g, "<code>$1</code>");
3065
+ return `<p>${inlineCode.replace(/\n/g, "<br/>")}</p>`;
3066
+ }).join("");
3067
+ return rendered;
3068
+ }
3069
+ function escapeHtml(value) {
3070
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
3071
+ }
2916
3072
 
2917
3073
  // src/config-service.ts
2918
3074
  var import_node_fs5 = __toESM(require("fs"));
@@ -4115,6 +4271,7 @@ var Orchestrator = class {
4115
4271
  commandPrefix;
4116
4272
  matrixUserId;
4117
4273
  sessionActiveWindowMs;
4274
+ groupDirectModeEnabled;
4118
4275
  defaultGroupTriggerPolicy;
4119
4276
  roomTriggerPolicies;
4120
4277
  configService;
@@ -4152,6 +4309,7 @@ var Orchestrator = class {
4152
4309
  this.matrixUserId = options?.matrixUserId ?? "";
4153
4310
  const sessionActiveWindowMinutes = options?.sessionActiveWindowMinutes ?? 20;
4154
4311
  this.sessionActiveWindowMs = Math.max(1, sessionActiveWindowMinutes) * 6e4;
4312
+ this.groupDirectModeEnabled = options?.groupDirectModeEnabled ?? false;
4155
4313
  this.defaultGroupTriggerPolicy = options?.defaultGroupTriggerPolicy ?? {
4156
4314
  allowMention: true,
4157
4315
  allowReply: true,
@@ -4486,7 +4644,7 @@ var Orchestrator = class {
4486
4644
  const prefixTriggered = prefixAllowed && this.commandPrefix.length > 0;
4487
4645
  const prefixedText = prefixTriggered ? extractCommandText(incomingTrimmed, this.commandPrefix) : null;
4488
4646
  const activeSession = message.isDirectMessage || groupPolicy?.allowActiveWindow ? this.stateStore.isSessionActive(sessionKey) : false;
4489
- const conversationalTrigger = message.isDirectMessage || Boolean(groupPolicy?.allowMention) && message.mentionsBot || Boolean(groupPolicy?.allowReply) && message.repliesToBot || activeSession;
4647
+ const conversationalTrigger = message.isDirectMessage || this.groupDirectModeEnabled || Boolean(groupPolicy?.allowMention) && message.mentionsBot || Boolean(groupPolicy?.allowReply) && message.repliesToBot || activeSession;
4490
4648
  if (!conversationalTrigger && prefixedText === null) {
4491
4649
  return { kind: "ignore" };
4492
4650
  }
@@ -4524,7 +4682,7 @@ var Orchestrator = class {
4524
4682
  }
4525
4683
  const status = this.stateStore.getSessionStatus(sessionKey);
4526
4684
  const roomConfig = this.resolveRoomRuntimeConfig(message.conversationId);
4527
- const scope = message.isDirectMessage ? "\u79C1\u804A\uFF08\u514D\u524D\u7F00\uFF09" : "\u7FA4\u804A\uFF08\u6309\u623F\u95F4\u89E6\u53D1\u7B56\u7565\uFF09";
4685
+ const scope = message.isDirectMessage ? "\u79C1\u804A\uFF08\u514D\u524D\u7F00\uFF09" : this.groupDirectModeEnabled ? "\u7FA4\u804A\uFF08\u9ED8\u8BA4\u76F4\u901A\uFF09" : "\u7FA4\u804A\uFF08\u6309\u623F\u95F4\u89E6\u53D1\u7B56\u7565\uFF09";
4528
4686
  const activeUntil = status.activeUntil ?? "\u672A\u6FC0\u6D3B";
4529
4687
  const metrics = this.metrics.snapshot(this.runningExecutions.size);
4530
4688
  const limiter = this.rateLimiter.snapshot();
@@ -5600,6 +5758,7 @@ var CodeHarborApp = class {
5600
5758
  commandPrefix: config.matrixCommandPrefix,
5601
5759
  matrixUserId: config.matrixUserId,
5602
5760
  sessionActiveWindowMinutes: config.sessionActiveWindowMinutes,
5761
+ groupDirectModeEnabled: config.groupDirectModeEnabled,
5603
5762
  defaultGroupTriggerPolicy: config.defaultGroupTriggerPolicy,
5604
5763
  roomTriggerPolicies: config.roomTriggerPolicies,
5605
5764
  rateLimiterOptions: config.rateLimiter,
@@ -5742,6 +5901,7 @@ var configSchema = import_zod.z.object({
5742
5901
  MATRIX_PROGRESS_MIN_INTERVAL_MS: import_zod.z.string().default("2500").transform((v) => Number.parseInt(v, 10)).pipe(import_zod.z.number().int().positive()),
5743
5902
  MATRIX_TYPING_TIMEOUT_MS: import_zod.z.string().default("10000").transform((v) => Number.parseInt(v, 10)).pipe(import_zod.z.number().int().positive()),
5744
5903
  SESSION_ACTIVE_WINDOW_MINUTES: import_zod.z.string().default("20").transform((v) => Number.parseInt(v, 10)).pipe(import_zod.z.number().int().positive()),
5904
+ GROUP_DIRECT_MODE_ENABLED: import_zod.z.string().default("false").transform((v) => v.toLowerCase() === "true"),
5745
5905
  GROUP_TRIGGER_ALLOW_MENTION: import_zod.z.string().default("true").transform((v) => v.toLowerCase() === "true"),
5746
5906
  GROUP_TRIGGER_ALLOW_REPLY: import_zod.z.string().default("true").transform((v) => v.toLowerCase() === "true"),
5747
5907
  GROUP_TRIGGER_ALLOW_ACTIVE_WINDOW: import_zod.z.string().default("true").transform((v) => v.toLowerCase() === "true"),
@@ -5796,6 +5956,7 @@ var configSchema = import_zod.z.object({
5796
5956
  matrixProgressMinIntervalMs: v.MATRIX_PROGRESS_MIN_INTERVAL_MS,
5797
5957
  matrixTypingTimeoutMs: v.MATRIX_TYPING_TIMEOUT_MS,
5798
5958
  sessionActiveWindowMinutes: v.SESSION_ACTIVE_WINDOW_MINUTES,
5959
+ groupDirectModeEnabled: v.GROUP_DIRECT_MODE_ENABLED,
5799
5960
  defaultGroupTriggerPolicy: {
5800
5961
  allowMention: v.GROUP_TRIGGER_ALLOW_MENTION,
5801
5962
  allowReply: v.GROUP_TRIGGER_ALLOW_REPLY,
@@ -5994,6 +6155,7 @@ var CONFIG_SNAPSHOT_ENV_KEYS = [
5994
6155
  "MATRIX_PROGRESS_MIN_INTERVAL_MS",
5995
6156
  "MATRIX_TYPING_TIMEOUT_MS",
5996
6157
  "SESSION_ACTIVE_WINDOW_MINUTES",
6158
+ "GROUP_DIRECT_MODE_ENABLED",
5997
6159
  "GROUP_TRIGGER_ALLOW_MENTION",
5998
6160
  "GROUP_TRIGGER_ALLOW_REPLY",
5999
6161
  "GROUP_TRIGGER_ALLOW_ACTIVE_WINDOW",
@@ -6059,6 +6221,7 @@ var envSnapshotSchema = import_zod2.z.object({
6059
6221
  MATRIX_PROGRESS_MIN_INTERVAL_MS: integerStringSchema("MATRIX_PROGRESS_MIN_INTERVAL_MS", 1),
6060
6222
  MATRIX_TYPING_TIMEOUT_MS: integerStringSchema("MATRIX_TYPING_TIMEOUT_MS", 1),
6061
6223
  SESSION_ACTIVE_WINDOW_MINUTES: integerStringSchema("SESSION_ACTIVE_WINDOW_MINUTES", 1),
6224
+ GROUP_DIRECT_MODE_ENABLED: booleanStringSchema("GROUP_DIRECT_MODE_ENABLED").default("false"),
6062
6225
  GROUP_TRIGGER_ALLOW_MENTION: booleanStringSchema("GROUP_TRIGGER_ALLOW_MENTION"),
6063
6226
  GROUP_TRIGGER_ALLOW_REPLY: booleanStringSchema("GROUP_TRIGGER_ALLOW_REPLY"),
6064
6227
  GROUP_TRIGGER_ALLOW_ACTIVE_WINDOW: booleanStringSchema("GROUP_TRIGGER_ALLOW_ACTIVE_WINDOW"),
@@ -6248,6 +6411,7 @@ function buildSnapshotEnv(config) {
6248
6411
  MATRIX_PROGRESS_MIN_INTERVAL_MS: String(config.matrixProgressMinIntervalMs),
6249
6412
  MATRIX_TYPING_TIMEOUT_MS: String(config.matrixTypingTimeoutMs),
6250
6413
  SESSION_ACTIVE_WINDOW_MINUTES: String(config.sessionActiveWindowMinutes),
6414
+ GROUP_DIRECT_MODE_ENABLED: String(config.groupDirectModeEnabled),
6251
6415
  GROUP_TRIGGER_ALLOW_MENTION: String(config.defaultGroupTriggerPolicy.allowMention),
6252
6416
  GROUP_TRIGGER_ALLOW_REPLY: String(config.defaultGroupTriggerPolicy.allowReply),
6253
6417
  GROUP_TRIGGER_ALLOW_ACTIVE_WINDOW: String(config.defaultGroupTriggerPolicy.allowActiveWindow),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeharbor",
3
- "version": "0.1.15",
3
+ "version": "0.1.16",
4
4
  "description": "Instant-messaging bridge for Codex CLI sessions",
5
5
  "license": "MIT",
6
6
  "main": "dist/cli.js",