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 +3 -0
- package/README.md +11 -0
- package/dist/cli.js +177 -13
- package/package.json +1 -1
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
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
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),
|