@hybridaione/hybridclaw 0.2.2 → 0.2.6
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/.github/workflows/ci.yml +70 -0
- package/.husky/pre-commit +1 -0
- package/CHANGELOG.md +85 -0
- package/CONTRIBUTING.md +33 -0
- package/README.md +41 -16
- package/SECURITY.md +17 -0
- package/biome.json +35 -0
- package/config.example.json +71 -8
- package/container/package-lock.json +2 -2
- package/container/package.json +1 -1
- package/container/src/approval-policy.ts +1303 -0
- package/container/src/browser-tools.ts +431 -136
- package/container/src/extensions.ts +36 -12
- package/container/src/hybridai-client.ts +34 -13
- package/container/src/index.ts +451 -109
- package/container/src/ipc.ts +5 -3
- package/container/src/token-usage.ts +20 -10
- package/container/src/tools.ts +599 -225
- package/container/src/types.ts +32 -2
- package/container/src/web-fetch.ts +89 -32
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +10 -2
- package/dist/agent.js.map +1 -1
- package/dist/audit-cli.d.ts.map +1 -1
- package/dist/audit-cli.js +4 -2
- package/dist/audit-cli.js.map +1 -1
- package/dist/audit-events.d.ts.map +1 -1
- package/dist/audit-events.js +53 -3
- package/dist/audit-events.js.map +1 -1
- package/dist/audit-trail.d.ts.map +1 -1
- package/dist/audit-trail.js +17 -8
- package/dist/audit-trail.js.map +1 -1
- package/dist/channels/discord/attachments.d.ts.map +1 -1
- package/dist/channels/discord/attachments.js +14 -7
- package/dist/channels/discord/attachments.js.map +1 -1
- package/dist/channels/discord/debounce.d.ts +9 -0
- package/dist/channels/discord/debounce.d.ts.map +1 -0
- package/dist/channels/discord/debounce.js +20 -0
- package/dist/channels/discord/debounce.js.map +1 -0
- package/dist/channels/discord/delivery.d.ts +4 -1
- package/dist/channels/discord/delivery.d.ts.map +1 -1
- package/dist/channels/discord/delivery.js +19 -3
- package/dist/channels/discord/delivery.js.map +1 -1
- package/dist/channels/discord/human-delay.d.ts +16 -0
- package/dist/channels/discord/human-delay.d.ts.map +1 -0
- package/dist/channels/discord/human-delay.js +29 -0
- package/dist/channels/discord/human-delay.js.map +1 -0
- package/dist/channels/discord/inbound.d.ts +4 -0
- package/dist/channels/discord/inbound.d.ts.map +1 -1
- package/dist/channels/discord/inbound.js +45 -4
- package/dist/channels/discord/inbound.js.map +1 -1
- package/dist/channels/discord/mentions.d.ts.map +1 -1
- package/dist/channels/discord/mentions.js +16 -4
- package/dist/channels/discord/mentions.js.map +1 -1
- package/dist/channels/discord/presence.d.ts +33 -0
- package/dist/channels/discord/presence.d.ts.map +1 -0
- package/dist/channels/discord/presence.js +111 -0
- package/dist/channels/discord/presence.js.map +1 -0
- package/dist/channels/discord/rate-limiter.d.ts +14 -0
- package/dist/channels/discord/rate-limiter.d.ts.map +1 -0
- package/dist/channels/discord/rate-limiter.js +49 -0
- package/dist/channels/discord/rate-limiter.js.map +1 -0
- package/dist/channels/discord/reactions.d.ts +38 -0
- package/dist/channels/discord/reactions.d.ts.map +1 -0
- package/dist/channels/discord/reactions.js +151 -0
- package/dist/channels/discord/reactions.js.map +1 -0
- package/dist/channels/discord/runtime.d.ts +6 -3
- package/dist/channels/discord/runtime.d.ts.map +1 -1
- package/dist/channels/discord/runtime.js +621 -125
- package/dist/channels/discord/runtime.js.map +1 -1
- package/dist/channels/discord/stream.d.ts +4 -1
- package/dist/channels/discord/stream.d.ts.map +1 -1
- package/dist/channels/discord/stream.js +16 -8
- package/dist/channels/discord/stream.js.map +1 -1
- package/dist/channels/discord/tool-actions.d.ts.map +1 -1
- package/dist/channels/discord/tool-actions.js +24 -12
- package/dist/channels/discord/tool-actions.js.map +1 -1
- package/dist/channels/discord/typing.d.ts +15 -0
- package/dist/channels/discord/typing.d.ts.map +1 -0
- package/dist/channels/discord/typing.js +106 -0
- package/dist/channels/discord/typing.js.map +1 -0
- package/dist/chunk.d.ts.map +1 -1
- package/dist/chunk.js +4 -2
- package/dist/chunk.js.map +1 -1
- package/dist/cli.js +47 -22
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +19 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +103 -18
- package/dist/config.js.map +1 -1
- package/dist/container-runner.d.ts.map +1 -1
- package/dist/container-runner.js +58 -26
- package/dist/container-runner.js.map +1 -1
- package/dist/container-setup.d.ts.map +1 -1
- package/dist/container-setup.js +10 -9
- package/dist/container-setup.js.map +1 -1
- package/dist/conversation.d.ts +2 -2
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +1 -1
- package/dist/conversation.js.map +1 -1
- package/dist/db.d.ts +118 -2
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +1568 -50
- package/dist/db.js.map +1 -1
- package/dist/delegation-manager.d.ts.map +1 -1
- package/dist/delegation-manager.js +3 -2
- package/dist/delegation-manager.js.map +1 -1
- package/dist/gateway-client.d.ts +2 -2
- package/dist/gateway-client.d.ts.map +1 -1
- package/dist/gateway-client.js +10 -4
- package/dist/gateway-client.js.map +1 -1
- package/dist/gateway-service.d.ts +3 -3
- package/dist/gateway-service.d.ts.map +1 -1
- package/dist/gateway-service.js +563 -73
- package/dist/gateway-service.js.map +1 -1
- package/dist/gateway-types.d.ts +24 -0
- package/dist/gateway-types.d.ts.map +1 -1
- package/dist/gateway-types.js.map +1 -1
- package/dist/gateway.js +179 -24
- package/dist/gateway.js.map +1 -1
- package/dist/health.d.ts.map +1 -1
- package/dist/health.js +20 -10
- package/dist/health.js.map +1 -1
- package/dist/heartbeat.d.ts +4 -0
- package/dist/heartbeat.d.ts.map +1 -1
- package/dist/heartbeat.js +48 -20
- package/dist/heartbeat.js.map +1 -1
- package/dist/hybridai-bots.d.ts.map +1 -1
- package/dist/hybridai-bots.js +4 -2
- package/dist/hybridai-bots.js.map +1 -1
- package/dist/instruction-approval-audit.d.ts.map +1 -1
- package/dist/instruction-approval-audit.js.map +1 -1
- package/dist/instruction-integrity.d.ts.map +1 -1
- package/dist/instruction-integrity.js +8 -2
- package/dist/instruction-integrity.js.map +1 -1
- package/dist/ipc.d.ts.map +1 -1
- package/dist/ipc.js +6 -1
- package/dist/ipc.js.map +1 -1
- package/dist/logger.js.map +1 -1
- package/dist/memory-consolidation.d.ts +17 -0
- package/dist/memory-consolidation.d.ts.map +1 -0
- package/dist/memory-consolidation.js +25 -0
- package/dist/memory-consolidation.js.map +1 -0
- package/dist/memory-service.d.ts +200 -0
- package/dist/memory-service.d.ts.map +1 -0
- package/dist/memory-service.js +294 -0
- package/dist/memory-service.js.map +1 -0
- package/dist/mount-security.d.ts.map +1 -1
- package/dist/mount-security.js +31 -7
- package/dist/mount-security.js.map +1 -1
- package/dist/observability-ingest.d.ts.map +1 -1
- package/dist/observability-ingest.js +32 -11
- package/dist/observability-ingest.js.map +1 -1
- package/dist/onboarding.d.ts.map +1 -1
- package/dist/onboarding.js +32 -9
- package/dist/onboarding.js.map +1 -1
- package/dist/proactive-policy.d.ts.map +1 -1
- package/dist/proactive-policy.js +2 -1
- package/dist/proactive-policy.js.map +1 -1
- package/dist/prompt-hooks.d.ts.map +1 -1
- package/dist/prompt-hooks.js +9 -7
- package/dist/prompt-hooks.js.map +1 -1
- package/dist/runtime-config.d.ts +98 -1
- package/dist/runtime-config.d.ts.map +1 -1
- package/dist/runtime-config.js +477 -23
- package/dist/runtime-config.js.map +1 -1
- package/dist/scheduled-task-runner.d.ts +1 -0
- package/dist/scheduled-task-runner.d.ts.map +1 -1
- package/dist/scheduled-task-runner.js +29 -10
- package/dist/scheduled-task-runner.js.map +1 -1
- package/dist/scheduler.d.ts +43 -4
- package/dist/scheduler.d.ts.map +1 -1
- package/dist/scheduler.js +530 -56
- package/dist/scheduler.js.map +1 -1
- package/dist/session-export.d.ts +26 -0
- package/dist/session-export.d.ts.map +1 -0
- package/dist/session-export.js +149 -0
- package/dist/session-export.js.map +1 -0
- package/dist/session-maintenance.d.ts.map +1 -1
- package/dist/session-maintenance.js +75 -13
- package/dist/session-maintenance.js.map +1 -1
- package/dist/session-transcripts.d.ts.map +1 -1
- package/dist/session-transcripts.js.map +1 -1
- package/dist/side-effects.d.ts.map +1 -1
- package/dist/side-effects.js +14 -2
- package/dist/side-effects.js.map +1 -1
- package/dist/skills-guard.d.ts.map +1 -1
- package/dist/skills-guard.js +893 -130
- package/dist/skills-guard.js.map +1 -1
- package/dist/skills.d.ts +5 -0
- package/dist/skills.d.ts.map +1 -1
- package/dist/skills.js +29 -15
- package/dist/skills.js.map +1 -1
- package/dist/token-efficiency.d.ts.map +1 -1
- package/dist/token-efficiency.js.map +1 -1
- package/dist/tui.js +92 -11
- package/dist/tui.js.map +1 -1
- package/dist/types.d.ts +146 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +24 -1
- package/dist/types.js.map +1 -1
- package/dist/update.d.ts.map +1 -1
- package/dist/update.js +42 -14
- package/dist/update.js.map +1 -1
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +49 -9
- package/dist/workspace.js.map +1 -1
- package/docs/chat.html +9 -3
- package/docs/index.html +37 -13
- package/package.json +8 -2
- package/src/agent.ts +16 -3
- package/src/audit-cli.ts +44 -16
- package/src/audit-events.ts +69 -5
- package/src/audit-trail.ts +41 -15
- package/src/channels/discord/attachments.ts +81 -27
- package/src/channels/discord/debounce.ts +25 -0
- package/src/channels/discord/delivery.ts +57 -13
- package/src/channels/discord/human-delay.ts +48 -0
- package/src/channels/discord/inbound.ts +66 -7
- package/src/channels/discord/mentions.ts +42 -18
- package/src/channels/discord/presence.ts +148 -0
- package/src/channels/discord/rate-limiter.ts +58 -0
- package/src/channels/discord/reactions.ts +211 -0
- package/src/channels/discord/runtime.ts +1048 -182
- package/src/channels/discord/stream.ts +73 -27
- package/src/channels/discord/tool-actions.ts +78 -37
- package/src/channels/discord/typing.ts +140 -0
- package/src/chunk.ts +12 -4
- package/src/cli.ts +141 -56
- package/src/config.ts +192 -34
- package/src/container-runner.ts +132 -42
- package/src/container-setup.ts +57 -22
- package/src/conversation.ts +9 -7
- package/src/db.ts +2217 -84
- package/src/delegation-manager.ts +6 -2
- package/src/gateway-client.ts +41 -17
- package/src/gateway-service.ts +1019 -201
- package/src/gateway-types.ts +33 -0
- package/src/gateway.ts +321 -48
- package/src/health.ts +66 -26
- package/src/heartbeat.ts +84 -22
- package/src/hybridai-bots.ts +14 -5
- package/src/instruction-approval-audit.ts +4 -1
- package/src/instruction-integrity.ts +30 -9
- package/src/ipc.ts +23 -5
- package/src/logger.ts +4 -1
- package/src/memory-consolidation.ts +41 -0
- package/src/memory-service.ts +606 -0
- package/src/mount-security.ts +58 -13
- package/src/observability-ingest.ts +134 -35
- package/src/onboarding.ts +126 -35
- package/src/proactive-policy.ts +3 -1
- package/src/prompt-hooks.ts +40 -17
- package/src/runtime-config.ts +1114 -99
- package/src/scheduled-task-runner.ts +63 -11
- package/src/scheduler.ts +683 -60
- package/src/session-export.ts +196 -0
- package/src/session-maintenance.ts +125 -22
- package/src/session-transcripts.ts +12 -3
- package/src/side-effects.ts +28 -5
- package/src/skills-guard.ts +1067 -219
- package/src/skills.ts +163 -65
- package/src/token-efficiency.ts +31 -9
- package/src/tui.ts +166 -25
- package/src/types.ts +195 -2
- package/src/update.ts +79 -23
- package/src/workspace.ts +63 -11
- package/tests/approval-policy.test.ts +224 -0
- package/tests/discord.basic.test.ts +82 -2
- package/tests/discord.human-presence.test.ts +85 -0
- package/tests/gateway-service.media-routing.test.ts +8 -2
- package/tests/memory-service.test.ts +1114 -0
- package/tests/token-efficiency.basic.test.ts +8 -2
- package/vitest.e2e.config.ts +3 -1
- package/vitest.integration.config.ts +3 -1
- package/vitest.live.config.ts +3 -1
- package/vitest.unit.config.ts +9 -0
package/dist/runtime-config.js
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
3
|
export const CONFIG_FILE_NAME = 'config.json';
|
|
4
|
-
export const CONFIG_VERSION =
|
|
4
|
+
export const CONFIG_VERSION = 5;
|
|
5
5
|
export const SECURITY_POLICY_VERSION = '2026-02-28';
|
|
6
|
-
const KNOWN_LOG_LEVELS = new Set([
|
|
6
|
+
const KNOWN_LOG_LEVELS = new Set([
|
|
7
|
+
'fatal',
|
|
8
|
+
'error',
|
|
9
|
+
'warn',
|
|
10
|
+
'info',
|
|
11
|
+
'debug',
|
|
12
|
+
'trace',
|
|
13
|
+
'silent',
|
|
14
|
+
]);
|
|
7
15
|
const DEFAULT_RUNTIME_CONFIG = {
|
|
8
16
|
version: CONFIG_VERSION,
|
|
9
17
|
security: {
|
|
@@ -22,6 +30,43 @@ const DEFAULT_RUNTIME_CONFIG = {
|
|
|
22
30
|
respondToAllMessages: false,
|
|
23
31
|
commandsOnly: false,
|
|
24
32
|
commandUserId: '',
|
|
33
|
+
groupPolicy: 'open',
|
|
34
|
+
freeResponseChannels: [],
|
|
35
|
+
humanDelay: {
|
|
36
|
+
mode: 'natural',
|
|
37
|
+
minMs: 800,
|
|
38
|
+
maxMs: 2_500,
|
|
39
|
+
},
|
|
40
|
+
typingMode: 'thinking',
|
|
41
|
+
presence: {
|
|
42
|
+
enabled: true,
|
|
43
|
+
intervalMs: 30_000,
|
|
44
|
+
healthyText: 'Watching the channels',
|
|
45
|
+
degradedText: 'Thinking slowly...',
|
|
46
|
+
exhaustedText: 'Taking a break',
|
|
47
|
+
activityType: 'watching',
|
|
48
|
+
},
|
|
49
|
+
lifecycleReactions: {
|
|
50
|
+
enabled: true,
|
|
51
|
+
removeOnComplete: true,
|
|
52
|
+
phases: {
|
|
53
|
+
queued: '⏳',
|
|
54
|
+
thinking: '🤔',
|
|
55
|
+
toolUse: '⚙️',
|
|
56
|
+
streaming: '✍️',
|
|
57
|
+
done: '✅',
|
|
58
|
+
error: '❌',
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
ackReaction: '👀',
|
|
62
|
+
ackReactionScope: 'group-mentions',
|
|
63
|
+
removeAckAfterReply: true,
|
|
64
|
+
debounceMs: 2_500,
|
|
65
|
+
rateLimitPerUser: 0,
|
|
66
|
+
rateLimitExemptRoles: [],
|
|
67
|
+
suppressPatterns: ['/stop', '/pause', 'brb', 'afk'],
|
|
68
|
+
maxConcurrentPerChannel: 2,
|
|
69
|
+
guilds: {},
|
|
25
70
|
},
|
|
26
71
|
hybridai: {
|
|
27
72
|
baseUrl: 'https://hybridai.one',
|
|
@@ -44,6 +89,10 @@ const DEFAULT_RUNTIME_CONFIG = {
|
|
|
44
89
|
intervalMs: 1_800_000,
|
|
45
90
|
channel: '',
|
|
46
91
|
},
|
|
92
|
+
memory: {
|
|
93
|
+
decayRate: 0.1,
|
|
94
|
+
consolidationIntervalHours: 24,
|
|
95
|
+
},
|
|
47
96
|
ops: {
|
|
48
97
|
healthHost: '127.0.0.1',
|
|
49
98
|
healthPort: 9090,
|
|
@@ -67,7 +116,9 @@ const DEFAULT_RUNTIME_CONFIG = {
|
|
|
67
116
|
},
|
|
68
117
|
sessionCompaction: {
|
|
69
118
|
enabled: true,
|
|
70
|
-
|
|
119
|
+
tokenBudget: 100_000,
|
|
120
|
+
budgetRatio: 0.7,
|
|
121
|
+
threshold: 200,
|
|
71
122
|
keepRecent: 40,
|
|
72
123
|
summaryMaxChars: 8_000,
|
|
73
124
|
preCompactionMemoryFlush: {
|
|
@@ -106,6 +157,9 @@ const DEFAULT_RUNTIME_CONFIG = {
|
|
|
106
157
|
maxIterations: 0,
|
|
107
158
|
},
|
|
108
159
|
},
|
|
160
|
+
scheduler: {
|
|
161
|
+
jobs: [],
|
|
162
|
+
},
|
|
109
163
|
};
|
|
110
164
|
const CONFIG_PATH = path.join(process.cwd(), CONFIG_FILE_NAME);
|
|
111
165
|
let currentConfig = cloneConfig(DEFAULT_RUNTIME_CONFIG);
|
|
@@ -164,6 +218,25 @@ function normalizeInteger(value, fallback, opts) {
|
|
|
164
218
|
parsed = opts.max;
|
|
165
219
|
return parsed;
|
|
166
220
|
}
|
|
221
|
+
function normalizeNumber(value, fallback, opts) {
|
|
222
|
+
let parsed;
|
|
223
|
+
if (typeof value === 'number') {
|
|
224
|
+
parsed = value;
|
|
225
|
+
}
|
|
226
|
+
else if (typeof value === 'string' && value.trim()) {
|
|
227
|
+
parsed = Number.parseFloat(value);
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
parsed = fallback;
|
|
231
|
+
}
|
|
232
|
+
if (!Number.isFinite(parsed))
|
|
233
|
+
parsed = fallback;
|
|
234
|
+
if (opts?.min != null && parsed < opts.min)
|
|
235
|
+
parsed = opts.min;
|
|
236
|
+
if (opts?.max != null && parsed > opts.max)
|
|
237
|
+
parsed = opts.max;
|
|
238
|
+
return parsed;
|
|
239
|
+
}
|
|
167
240
|
function normalizeStringArray(value, fallback) {
|
|
168
241
|
if (Array.isArray(value)) {
|
|
169
242
|
const normalized = value
|
|
@@ -182,8 +255,337 @@ function normalizeStringArray(value, fallback) {
|
|
|
182
255
|
}
|
|
183
256
|
return fallback;
|
|
184
257
|
}
|
|
258
|
+
function normalizeDiscordGroupPolicy(value, fallback) {
|
|
259
|
+
if (typeof value !== 'string')
|
|
260
|
+
return fallback;
|
|
261
|
+
const normalized = value.trim().toLowerCase();
|
|
262
|
+
if (normalized === 'open' ||
|
|
263
|
+
normalized === 'allowlist' ||
|
|
264
|
+
normalized === 'disabled') {
|
|
265
|
+
return normalized;
|
|
266
|
+
}
|
|
267
|
+
return fallback;
|
|
268
|
+
}
|
|
269
|
+
function normalizeDiscordChannelMode(value, fallback) {
|
|
270
|
+
if (typeof value !== 'string')
|
|
271
|
+
return fallback;
|
|
272
|
+
const normalized = value.trim().toLowerCase();
|
|
273
|
+
if (normalized === 'off' || normalized === 'mention' || normalized === 'free')
|
|
274
|
+
return normalized;
|
|
275
|
+
if (normalized === 'free-response' || normalized === 'free_response')
|
|
276
|
+
return 'free';
|
|
277
|
+
return fallback;
|
|
278
|
+
}
|
|
279
|
+
function normalizeDiscordTypingMode(value, fallback) {
|
|
280
|
+
if (typeof value !== 'string')
|
|
281
|
+
return fallback;
|
|
282
|
+
const normalized = value.trim().toLowerCase();
|
|
283
|
+
if (normalized === 'instant' ||
|
|
284
|
+
normalized === 'thinking' ||
|
|
285
|
+
normalized === 'streaming' ||
|
|
286
|
+
normalized === 'never') {
|
|
287
|
+
return normalized;
|
|
288
|
+
}
|
|
289
|
+
return fallback;
|
|
290
|
+
}
|
|
291
|
+
function normalizeDiscordHumanDelayMode(value, fallback) {
|
|
292
|
+
if (typeof value !== 'string')
|
|
293
|
+
return fallback;
|
|
294
|
+
const normalized = value.trim().toLowerCase();
|
|
295
|
+
if (normalized === 'off' ||
|
|
296
|
+
normalized === 'natural' ||
|
|
297
|
+
normalized === 'custom') {
|
|
298
|
+
return normalized;
|
|
299
|
+
}
|
|
300
|
+
return fallback;
|
|
301
|
+
}
|
|
302
|
+
function normalizeDiscordAckReactionScope(value, fallback) {
|
|
303
|
+
if (typeof value !== 'string')
|
|
304
|
+
return fallback;
|
|
305
|
+
const normalized = value.trim().toLowerCase();
|
|
306
|
+
if (normalized === 'all' ||
|
|
307
|
+
normalized === 'group-mentions' ||
|
|
308
|
+
normalized === 'direct' ||
|
|
309
|
+
normalized === 'off') {
|
|
310
|
+
return normalized;
|
|
311
|
+
}
|
|
312
|
+
return fallback;
|
|
313
|
+
}
|
|
314
|
+
function normalizeDiscordPresenceActivityType(value, fallback) {
|
|
315
|
+
if (typeof value !== 'string')
|
|
316
|
+
return fallback;
|
|
317
|
+
const normalized = value.trim().toLowerCase();
|
|
318
|
+
if (normalized === 'playing' ||
|
|
319
|
+
normalized === 'watching' ||
|
|
320
|
+
normalized === 'listening' ||
|
|
321
|
+
normalized === 'competing' ||
|
|
322
|
+
normalized === 'custom') {
|
|
323
|
+
return normalized;
|
|
324
|
+
}
|
|
325
|
+
return fallback;
|
|
326
|
+
}
|
|
327
|
+
function normalizeDiscordHumanDelayConfig(value, fallback) {
|
|
328
|
+
const raw = isRecord(value) ? value : {};
|
|
329
|
+
const mode = normalizeDiscordHumanDelayMode(raw.mode, fallback.mode);
|
|
330
|
+
const minMs = normalizeInteger(raw.minMs, fallback.minMs, {
|
|
331
|
+
min: 0,
|
|
332
|
+
max: 120_000,
|
|
333
|
+
});
|
|
334
|
+
const maxMsRaw = normalizeInteger(raw.maxMs, fallback.maxMs, {
|
|
335
|
+
min: 0,
|
|
336
|
+
max: 120_000,
|
|
337
|
+
});
|
|
338
|
+
const maxMs = Math.max(minMs, maxMsRaw);
|
|
339
|
+
return { mode, minMs, maxMs };
|
|
340
|
+
}
|
|
341
|
+
function normalizeDiscordPresenceConfig(value, fallback) {
|
|
342
|
+
const raw = isRecord(value) ? value : {};
|
|
343
|
+
return {
|
|
344
|
+
enabled: normalizeBoolean(raw.enabled, fallback.enabled),
|
|
345
|
+
intervalMs: normalizeInteger(raw.intervalMs, fallback.intervalMs, {
|
|
346
|
+
min: 5_000,
|
|
347
|
+
max: 300_000,
|
|
348
|
+
}),
|
|
349
|
+
healthyText: normalizeString(raw.healthyText, fallback.healthyText, {
|
|
350
|
+
allowEmpty: false,
|
|
351
|
+
}),
|
|
352
|
+
degradedText: normalizeString(raw.degradedText, fallback.degradedText, {
|
|
353
|
+
allowEmpty: false,
|
|
354
|
+
}),
|
|
355
|
+
exhaustedText: normalizeString(raw.exhaustedText, fallback.exhaustedText, {
|
|
356
|
+
allowEmpty: false,
|
|
357
|
+
}),
|
|
358
|
+
activityType: normalizeDiscordPresenceActivityType(raw.activityType, fallback.activityType),
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
function normalizeDiscordLifecycleReactionsConfig(value, fallback) {
|
|
362
|
+
const raw = isRecord(value) ? value : {};
|
|
363
|
+
const rawPhases = isRecord(raw.phases) ? raw.phases : {};
|
|
364
|
+
return {
|
|
365
|
+
enabled: normalizeBoolean(raw.enabled, fallback.enabled),
|
|
366
|
+
removeOnComplete: normalizeBoolean(raw.removeOnComplete, fallback.removeOnComplete),
|
|
367
|
+
phases: {
|
|
368
|
+
queued: normalizeString(rawPhases.queued, fallback.phases.queued, {
|
|
369
|
+
allowEmpty: false,
|
|
370
|
+
}),
|
|
371
|
+
thinking: normalizeString(rawPhases.thinking, fallback.phases.thinking, {
|
|
372
|
+
allowEmpty: false,
|
|
373
|
+
}),
|
|
374
|
+
toolUse: normalizeString(rawPhases.toolUse, fallback.phases.toolUse, {
|
|
375
|
+
allowEmpty: false,
|
|
376
|
+
}),
|
|
377
|
+
streaming: normalizeString(rawPhases.streaming, fallback.phases.streaming, { allowEmpty: false }),
|
|
378
|
+
done: normalizeString(rawPhases.done, fallback.phases.done, {
|
|
379
|
+
allowEmpty: false,
|
|
380
|
+
}),
|
|
381
|
+
error: normalizeString(rawPhases.error, fallback.phases.error, {
|
|
382
|
+
allowEmpty: false,
|
|
383
|
+
}),
|
|
384
|
+
},
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
function normalizeDiscordChannelConfig(value, fallback, defaultMode) {
|
|
388
|
+
const channelFallback = {
|
|
389
|
+
...fallback,
|
|
390
|
+
mode: fallback.mode || defaultMode,
|
|
391
|
+
};
|
|
392
|
+
if (typeof value === 'string') {
|
|
393
|
+
return { mode: normalizeDiscordChannelMode(value, channelFallback.mode) };
|
|
394
|
+
}
|
|
395
|
+
if (!isRecord(value))
|
|
396
|
+
return null;
|
|
397
|
+
const channelConfig = {
|
|
398
|
+
mode: normalizeDiscordChannelMode(value.mode, channelFallback.mode),
|
|
399
|
+
};
|
|
400
|
+
if (value.typingMode !== undefined ||
|
|
401
|
+
channelFallback.typingMode !== undefined) {
|
|
402
|
+
channelConfig.typingMode = normalizeDiscordTypingMode(value.typingMode, channelFallback.typingMode ?? DEFAULT_RUNTIME_CONFIG.discord.typingMode);
|
|
403
|
+
}
|
|
404
|
+
if (value.debounceMs !== undefined ||
|
|
405
|
+
channelFallback.debounceMs !== undefined) {
|
|
406
|
+
channelConfig.debounceMs = normalizeInteger(value.debounceMs, channelFallback.debounceMs ?? DEFAULT_RUNTIME_CONFIG.discord.debounceMs, { min: 0, max: 120_000 });
|
|
407
|
+
}
|
|
408
|
+
if (value.ackReaction !== undefined ||
|
|
409
|
+
channelFallback.ackReaction !== undefined) {
|
|
410
|
+
channelConfig.ackReaction = normalizeString(value.ackReaction, channelFallback.ackReaction ?? DEFAULT_RUNTIME_CONFIG.discord.ackReaction, { allowEmpty: false });
|
|
411
|
+
}
|
|
412
|
+
if (value.ackReactionScope !== undefined ||
|
|
413
|
+
channelFallback.ackReactionScope !== undefined) {
|
|
414
|
+
channelConfig.ackReactionScope = normalizeDiscordAckReactionScope(value.ackReactionScope, channelFallback.ackReactionScope ??
|
|
415
|
+
DEFAULT_RUNTIME_CONFIG.discord.ackReactionScope);
|
|
416
|
+
}
|
|
417
|
+
if (value.removeAckAfterReply !== undefined ||
|
|
418
|
+
channelFallback.removeAckAfterReply !== undefined) {
|
|
419
|
+
channelConfig.removeAckAfterReply = normalizeBoolean(value.removeAckAfterReply, channelFallback.removeAckAfterReply ??
|
|
420
|
+
DEFAULT_RUNTIME_CONFIG.discord.removeAckAfterReply);
|
|
421
|
+
}
|
|
422
|
+
if (value.humanDelay !== undefined ||
|
|
423
|
+
channelFallback.humanDelay !== undefined) {
|
|
424
|
+
channelConfig.humanDelay = normalizeDiscordHumanDelayConfig(value.humanDelay, channelFallback.humanDelay ?? DEFAULT_RUNTIME_CONFIG.discord.humanDelay);
|
|
425
|
+
}
|
|
426
|
+
if (value.rateLimitPerUser !== undefined ||
|
|
427
|
+
channelFallback.rateLimitPerUser !== undefined) {
|
|
428
|
+
channelConfig.rateLimitPerUser = normalizeInteger(value.rateLimitPerUser, channelFallback.rateLimitPerUser ??
|
|
429
|
+
DEFAULT_RUNTIME_CONFIG.discord.rateLimitPerUser, { min: 0, max: 300 });
|
|
430
|
+
}
|
|
431
|
+
if (value.suppressPatterns !== undefined ||
|
|
432
|
+
channelFallback.suppressPatterns !== undefined) {
|
|
433
|
+
channelConfig.suppressPatterns = normalizeStringArray(value.suppressPatterns, channelFallback.suppressPatterns ??
|
|
434
|
+
DEFAULT_RUNTIME_CONFIG.discord.suppressPatterns);
|
|
435
|
+
}
|
|
436
|
+
if (value.maxConcurrentPerChannel !== undefined ||
|
|
437
|
+
channelFallback.maxConcurrentPerChannel !== undefined) {
|
|
438
|
+
channelConfig.maxConcurrentPerChannel = normalizeInteger(value.maxConcurrentPerChannel, channelFallback.maxConcurrentPerChannel ??
|
|
439
|
+
DEFAULT_RUNTIME_CONFIG.discord.maxConcurrentPerChannel, { min: 1, max: 16 });
|
|
440
|
+
}
|
|
441
|
+
return channelConfig;
|
|
442
|
+
}
|
|
443
|
+
function normalizeDiscordGuildConfig(value, fallback) {
|
|
444
|
+
if (!isRecord(value))
|
|
445
|
+
return fallback;
|
|
446
|
+
const defaultMode = normalizeDiscordChannelMode(value.defaultMode, fallback.defaultMode);
|
|
447
|
+
const rawChannels = isRecord(value.channels) ? value.channels : {};
|
|
448
|
+
const channels = {};
|
|
449
|
+
for (const [rawChannelId, rawChannelConfig] of Object.entries(rawChannels)) {
|
|
450
|
+
const channelId = rawChannelId.trim();
|
|
451
|
+
if (!channelId)
|
|
452
|
+
continue;
|
|
453
|
+
const fallbackChannel = fallback.channels[channelId] ?? {
|
|
454
|
+
mode: defaultMode,
|
|
455
|
+
};
|
|
456
|
+
const channelConfig = normalizeDiscordChannelConfig(rawChannelConfig, fallbackChannel, defaultMode);
|
|
457
|
+
if (!channelConfig)
|
|
458
|
+
continue;
|
|
459
|
+
channels[channelId] = channelConfig;
|
|
460
|
+
}
|
|
461
|
+
return { defaultMode, channels };
|
|
462
|
+
}
|
|
463
|
+
function normalizeDiscordGuildMap(value, fallback) {
|
|
464
|
+
if (!isRecord(value))
|
|
465
|
+
return fallback;
|
|
466
|
+
const guilds = {};
|
|
467
|
+
for (const [rawGuildId, rawGuildConfig] of Object.entries(value)) {
|
|
468
|
+
const guildId = rawGuildId.trim();
|
|
469
|
+
if (!guildId)
|
|
470
|
+
continue;
|
|
471
|
+
const fallbackGuild = fallback[guildId] ?? {
|
|
472
|
+
defaultMode: 'mention',
|
|
473
|
+
channels: {},
|
|
474
|
+
};
|
|
475
|
+
guilds[guildId] = normalizeDiscordGuildConfig(rawGuildConfig, fallbackGuild);
|
|
476
|
+
}
|
|
477
|
+
return guilds;
|
|
478
|
+
}
|
|
479
|
+
function normalizeSchedulerScheduleKind(value, fallback) {
|
|
480
|
+
if (typeof value !== 'string')
|
|
481
|
+
return fallback;
|
|
482
|
+
const normalized = value.trim().toLowerCase();
|
|
483
|
+
if (normalized === 'at' || normalized === 'every' || normalized === 'cron')
|
|
484
|
+
return normalized;
|
|
485
|
+
return fallback;
|
|
486
|
+
}
|
|
487
|
+
function normalizeSchedulerActionKind(value, fallback) {
|
|
488
|
+
if (typeof value !== 'string')
|
|
489
|
+
return fallback;
|
|
490
|
+
const normalized = value.trim().toLowerCase();
|
|
491
|
+
if (normalized === 'agent_turn' || normalized === 'system_event')
|
|
492
|
+
return normalized;
|
|
493
|
+
return fallback;
|
|
494
|
+
}
|
|
495
|
+
function normalizeSchedulerDeliveryKind(value, fallback) {
|
|
496
|
+
if (typeof value !== 'string')
|
|
497
|
+
return fallback;
|
|
498
|
+
const normalized = value.trim().toLowerCase();
|
|
499
|
+
if (normalized === 'channel' ||
|
|
500
|
+
normalized === 'last-channel' ||
|
|
501
|
+
normalized === 'webhook')
|
|
502
|
+
return normalized;
|
|
503
|
+
return fallback;
|
|
504
|
+
}
|
|
505
|
+
function normalizeSchedulerJobList(value, fallback) {
|
|
506
|
+
if (!Array.isArray(value))
|
|
507
|
+
return fallback;
|
|
508
|
+
const jobs = [];
|
|
509
|
+
for (const item of value) {
|
|
510
|
+
if (!isRecord(item))
|
|
511
|
+
continue;
|
|
512
|
+
const jobId = normalizeString(item.id, '', { allowEmpty: false });
|
|
513
|
+
if (!jobId)
|
|
514
|
+
continue;
|
|
515
|
+
const rawSchedule = isRecord(item.schedule) ? item.schedule : {};
|
|
516
|
+
const rawAction = isRecord(item.action) ? item.action : {};
|
|
517
|
+
const rawDelivery = isRecord(item.delivery) ? item.delivery : {};
|
|
518
|
+
const scheduleKind = normalizeSchedulerScheduleKind(rawSchedule.kind, 'cron');
|
|
519
|
+
const everyMs = scheduleKind === 'every'
|
|
520
|
+
? normalizeInteger(rawSchedule.everyMs, 60_000, {
|
|
521
|
+
min: 10_000,
|
|
522
|
+
max: 86_400_000,
|
|
523
|
+
})
|
|
524
|
+
: null;
|
|
525
|
+
const atRaw = scheduleKind === 'at'
|
|
526
|
+
? normalizeString(rawSchedule.at, '', { allowEmpty: false })
|
|
527
|
+
: '';
|
|
528
|
+
const atIso = scheduleKind === 'at'
|
|
529
|
+
? (() => {
|
|
530
|
+
const parsed = new Date(atRaw);
|
|
531
|
+
return Number.isNaN(parsed.getTime()) ? null : parsed.toISOString();
|
|
532
|
+
})()
|
|
533
|
+
: null;
|
|
534
|
+
const expr = scheduleKind === 'cron'
|
|
535
|
+
? normalizeString(rawSchedule.expr, '', { allowEmpty: false })
|
|
536
|
+
: '';
|
|
537
|
+
if (scheduleKind === 'at' && !atIso)
|
|
538
|
+
continue;
|
|
539
|
+
if (scheduleKind === 'cron' && !expr)
|
|
540
|
+
continue;
|
|
541
|
+
const deliveryKind = normalizeSchedulerDeliveryKind(rawDelivery.kind, 'channel');
|
|
542
|
+
const to = normalizeString(rawDelivery.to, '', { allowEmpty: true });
|
|
543
|
+
const webhookUrl = normalizeString(rawDelivery.webhookUrl ?? rawDelivery.url, '', { allowEmpty: true });
|
|
544
|
+
if (deliveryKind === 'channel' && !to)
|
|
545
|
+
continue;
|
|
546
|
+
if (deliveryKind === 'webhook' && !webhookUrl)
|
|
547
|
+
continue;
|
|
548
|
+
const actionMessage = normalizeString(rawAction.message, '', {
|
|
549
|
+
allowEmpty: false,
|
|
550
|
+
});
|
|
551
|
+
if (!actionMessage)
|
|
552
|
+
continue;
|
|
553
|
+
const name = normalizeString(item.name, '', { allowEmpty: true });
|
|
554
|
+
const description = normalizeString(item.description, '', {
|
|
555
|
+
allowEmpty: true,
|
|
556
|
+
});
|
|
557
|
+
jobs.push({
|
|
558
|
+
id: jobId,
|
|
559
|
+
...(name ? { name } : {}),
|
|
560
|
+
...(description ? { description } : {}),
|
|
561
|
+
schedule: {
|
|
562
|
+
kind: scheduleKind,
|
|
563
|
+
at: atIso,
|
|
564
|
+
everyMs,
|
|
565
|
+
expr: scheduleKind === 'cron' ? expr : null,
|
|
566
|
+
tz: normalizeString(rawSchedule.tz, '', { allowEmpty: true }),
|
|
567
|
+
},
|
|
568
|
+
action: {
|
|
569
|
+
kind: normalizeSchedulerActionKind(rawAction.kind, 'agent_turn'),
|
|
570
|
+
message: actionMessage,
|
|
571
|
+
},
|
|
572
|
+
delivery: {
|
|
573
|
+
kind: deliveryKind,
|
|
574
|
+
channel: normalizeString(rawDelivery.channel, 'discord', {
|
|
575
|
+
allowEmpty: true,
|
|
576
|
+
}),
|
|
577
|
+
to,
|
|
578
|
+
webhookUrl,
|
|
579
|
+
},
|
|
580
|
+
enabled: normalizeBoolean(item.enabled, true),
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
return jobs;
|
|
584
|
+
}
|
|
185
585
|
function normalizeLogLevel(value, fallback) {
|
|
186
|
-
const normalized = normalizeString(value, fallback, {
|
|
586
|
+
const normalized = normalizeString(value, fallback, {
|
|
587
|
+
allowEmpty: false,
|
|
588
|
+
}).toLowerCase();
|
|
187
589
|
if (KNOWN_LOG_LEVELS.has(normalized))
|
|
188
590
|
return normalized;
|
|
189
591
|
return fallback;
|
|
@@ -193,7 +595,10 @@ function normalizeBaseUrl(value, fallback) {
|
|
|
193
595
|
return candidate.replace(/\/+$/, '') || fallback;
|
|
194
596
|
}
|
|
195
597
|
function normalizeApiPath(value, fallback) {
|
|
196
|
-
const normalized = normalizeString(value, fallback, {
|
|
598
|
+
const normalized = normalizeString(value, fallback, {
|
|
599
|
+
allowEmpty: false,
|
|
600
|
+
trim: true,
|
|
601
|
+
});
|
|
197
602
|
if (/^https?:\/\//i.test(normalized)) {
|
|
198
603
|
return normalized.replace(/\/+$/, '');
|
|
199
604
|
}
|
|
@@ -214,24 +619,36 @@ function normalizeRuntimeConfig(patch) {
|
|
|
214
619
|
const rawHybridAi = isRecord(raw.hybridai) ? raw.hybridai : {};
|
|
215
620
|
const rawContainer = isRecord(raw.container) ? raw.container : {};
|
|
216
621
|
const rawHeartbeat = isRecord(raw.heartbeat) ? raw.heartbeat : {};
|
|
622
|
+
const rawMemory = isRecord(raw.memory) ? raw.memory : {};
|
|
217
623
|
const rawOps = isRecord(raw.ops) ? raw.ops : {};
|
|
218
624
|
const rawObservability = isRecord(raw.observability) ? raw.observability : {};
|
|
219
|
-
const rawSessionCompaction = isRecord(raw.sessionCompaction)
|
|
625
|
+
const rawSessionCompaction = isRecord(raw.sessionCompaction)
|
|
626
|
+
? raw.sessionCompaction
|
|
627
|
+
: {};
|
|
220
628
|
const rawPreFlush = isRecord(rawSessionCompaction.preCompactionMemoryFlush)
|
|
221
629
|
? rawSessionCompaction.preCompactionMemoryFlush
|
|
222
630
|
: {};
|
|
223
631
|
const rawPromptHooks = isRecord(raw.promptHooks) ? raw.promptHooks : {};
|
|
224
632
|
const rawProactive = isRecord(raw.proactive) ? raw.proactive : {};
|
|
225
|
-
const rawActiveHours = isRecord(rawProactive.activeHours)
|
|
226
|
-
|
|
227
|
-
|
|
633
|
+
const rawActiveHours = isRecord(rawProactive.activeHours)
|
|
634
|
+
? rawProactive.activeHours
|
|
635
|
+
: {};
|
|
636
|
+
const rawDelegation = isRecord(rawProactive.delegation)
|
|
637
|
+
? rawProactive.delegation
|
|
638
|
+
: {};
|
|
639
|
+
const rawAutoRetry = isRecord(rawProactive.autoRetry)
|
|
640
|
+
? rawProactive.autoRetry
|
|
641
|
+
: {};
|
|
228
642
|
const rawRalph = isRecord(rawProactive.ralph) ? rawProactive.ralph : {};
|
|
643
|
+
const rawScheduler = isRecord(raw.scheduler) ? raw.scheduler : {};
|
|
229
644
|
const defaultOps = DEFAULT_RUNTIME_CONFIG.ops;
|
|
230
645
|
const healthPort = normalizeInteger(rawOps.healthPort, defaultOps.healthPort, { min: 1, max: 65_535 });
|
|
231
646
|
const webApiToken = normalizeString(rawOps.webApiToken, defaultOps.webApiToken, { allowEmpty: true });
|
|
232
647
|
const hybridBaseUrl = normalizeBaseUrl(rawHybridAi.baseUrl, DEFAULT_RUNTIME_CONFIG.hybridai.baseUrl);
|
|
233
648
|
const hybridDefaultChatbotId = normalizeString(rawHybridAi.defaultChatbotId, DEFAULT_RUNTIME_CONFIG.hybridai.defaultChatbotId, { allowEmpty: true });
|
|
234
649
|
const threshold = normalizeInteger(rawSessionCompaction.threshold, DEFAULT_RUNTIME_CONFIG.sessionCompaction.threshold, { min: 20 });
|
|
650
|
+
const tokenBudget = normalizeInteger(rawSessionCompaction.tokenBudget, DEFAULT_RUNTIME_CONFIG.sessionCompaction.tokenBudget, { min: 1_000 });
|
|
651
|
+
const budgetRatio = normalizeNumber(rawSessionCompaction.budgetRatio, DEFAULT_RUNTIME_CONFIG.sessionCompaction.budgetRatio, { min: 0.05, max: 1 });
|
|
235
652
|
const keepRecentRaw = normalizeInteger(rawSessionCompaction.keepRecent, DEFAULT_RUNTIME_CONFIG.sessionCompaction.keepRecent, { min: 1 });
|
|
236
653
|
const keepRecent = Math.min(keepRecentRaw, Math.max(1, threshold - 1));
|
|
237
654
|
const modelList = normalizeStringArray(rawHybridAi.models, DEFAULT_RUNTIME_CONFIG.hybridai.models);
|
|
@@ -253,6 +670,21 @@ function normalizeRuntimeConfig(patch) {
|
|
|
253
670
|
respondToAllMessages: normalizeBoolean(rawDiscord.respondToAllMessages, DEFAULT_RUNTIME_CONFIG.discord.respondToAllMessages),
|
|
254
671
|
commandsOnly: normalizeBoolean(rawDiscord.commandsOnly, DEFAULT_RUNTIME_CONFIG.discord.commandsOnly),
|
|
255
672
|
commandUserId: normalizeString(rawDiscord.commandUserId, DEFAULT_RUNTIME_CONFIG.discord.commandUserId, { allowEmpty: true }),
|
|
673
|
+
groupPolicy: normalizeDiscordGroupPolicy(rawDiscord.groupPolicy, DEFAULT_RUNTIME_CONFIG.discord.groupPolicy),
|
|
674
|
+
freeResponseChannels: normalizeStringArray(rawDiscord.freeResponseChannels, DEFAULT_RUNTIME_CONFIG.discord.freeResponseChannels),
|
|
675
|
+
humanDelay: normalizeDiscordHumanDelayConfig(rawDiscord.humanDelay, DEFAULT_RUNTIME_CONFIG.discord.humanDelay),
|
|
676
|
+
typingMode: normalizeDiscordTypingMode(rawDiscord.typingMode, DEFAULT_RUNTIME_CONFIG.discord.typingMode),
|
|
677
|
+
presence: normalizeDiscordPresenceConfig(rawDiscord.presence, DEFAULT_RUNTIME_CONFIG.discord.presence),
|
|
678
|
+
lifecycleReactions: normalizeDiscordLifecycleReactionsConfig(rawDiscord.lifecycleReactions, DEFAULT_RUNTIME_CONFIG.discord.lifecycleReactions),
|
|
679
|
+
ackReaction: normalizeString(rawDiscord.ackReaction, DEFAULT_RUNTIME_CONFIG.discord.ackReaction, { allowEmpty: false }),
|
|
680
|
+
ackReactionScope: normalizeDiscordAckReactionScope(rawDiscord.ackReactionScope, DEFAULT_RUNTIME_CONFIG.discord.ackReactionScope),
|
|
681
|
+
removeAckAfterReply: normalizeBoolean(rawDiscord.removeAckAfterReply, DEFAULT_RUNTIME_CONFIG.discord.removeAckAfterReply),
|
|
682
|
+
debounceMs: normalizeInteger(rawDiscord.debounceMs, DEFAULT_RUNTIME_CONFIG.discord.debounceMs, { min: 0, max: 120_000 }),
|
|
683
|
+
rateLimitPerUser: normalizeInteger(rawDiscord.rateLimitPerUser, DEFAULT_RUNTIME_CONFIG.discord.rateLimitPerUser, { min: 0, max: 300 }),
|
|
684
|
+
rateLimitExemptRoles: normalizeStringArray(rawDiscord.rateLimitExemptRoles, DEFAULT_RUNTIME_CONFIG.discord.rateLimitExemptRoles),
|
|
685
|
+
suppressPatterns: normalizeStringArray(rawDiscord.suppressPatterns, DEFAULT_RUNTIME_CONFIG.discord.suppressPatterns),
|
|
686
|
+
maxConcurrentPerChannel: normalizeInteger(rawDiscord.maxConcurrentPerChannel, DEFAULT_RUNTIME_CONFIG.discord.maxConcurrentPerChannel, { min: 1, max: 16 }),
|
|
687
|
+
guilds: normalizeDiscordGuildMap(rawDiscord.guilds, DEFAULT_RUNTIME_CONFIG.discord.guilds),
|
|
256
688
|
},
|
|
257
689
|
hybridai: {
|
|
258
690
|
baseUrl: hybridBaseUrl,
|
|
@@ -275,13 +707,23 @@ function normalizeRuntimeConfig(patch) {
|
|
|
275
707
|
intervalMs: normalizeInteger(rawHeartbeat.intervalMs, DEFAULT_RUNTIME_CONFIG.heartbeat.intervalMs, { min: 10_000 }),
|
|
276
708
|
channel: normalizeString(rawHeartbeat.channel, DEFAULT_RUNTIME_CONFIG.heartbeat.channel, { allowEmpty: true }),
|
|
277
709
|
},
|
|
710
|
+
memory: {
|
|
711
|
+
decayRate: normalizeNumber(rawMemory.decayRate, DEFAULT_RUNTIME_CONFIG.memory.decayRate, { min: 0, max: 0.95 }),
|
|
712
|
+
consolidationIntervalHours: normalizeInteger(rawMemory.consolidationIntervalHours, DEFAULT_RUNTIME_CONFIG.memory.consolidationIntervalHours, { min: 0, max: 24 * 30 }),
|
|
713
|
+
},
|
|
278
714
|
ops: {
|
|
279
|
-
healthHost: normalizeString(rawOps.healthHost, defaultOps.healthHost, {
|
|
715
|
+
healthHost: normalizeString(rawOps.healthHost, defaultOps.healthHost, {
|
|
716
|
+
allowEmpty: false,
|
|
717
|
+
}),
|
|
280
718
|
healthPort,
|
|
281
719
|
webApiToken,
|
|
282
720
|
gatewayBaseUrl: normalizeBaseUrl(rawOps.gatewayBaseUrl, `http://127.0.0.1:${healthPort}`),
|
|
283
|
-
gatewayApiToken: normalizeString(rawOps.gatewayApiToken, webApiToken, {
|
|
284
|
-
|
|
721
|
+
gatewayApiToken: normalizeString(rawOps.gatewayApiToken, webApiToken, {
|
|
722
|
+
allowEmpty: true,
|
|
723
|
+
}),
|
|
724
|
+
dbPath: normalizeString(rawOps.dbPath, defaultOps.dbPath, {
|
|
725
|
+
allowEmpty: false,
|
|
726
|
+
}),
|
|
285
727
|
logLevel: normalizeLogLevel(rawOps.logLevel, defaultOps.logLevel),
|
|
286
728
|
},
|
|
287
729
|
observability: {
|
|
@@ -289,7 +731,9 @@ function normalizeRuntimeConfig(patch) {
|
|
|
289
731
|
baseUrl: normalizeBaseUrl(rawObservability.baseUrl, hybridBaseUrl),
|
|
290
732
|
ingestPath: normalizeApiPath(rawObservability.ingestPath, DEFAULT_RUNTIME_CONFIG.observability.ingestPath),
|
|
291
733
|
statusPath: normalizeApiPath(rawObservability.statusPath, DEFAULT_RUNTIME_CONFIG.observability.statusPath),
|
|
292
|
-
botId: normalizeString(rawObservability.botId, hybridDefaultChatbotId, {
|
|
734
|
+
botId: normalizeString(rawObservability.botId, hybridDefaultChatbotId, {
|
|
735
|
+
allowEmpty: true,
|
|
736
|
+
}),
|
|
293
737
|
agentId: normalizeString(rawObservability.agentId, DEFAULT_RUNTIME_CONFIG.observability.agentId, { allowEmpty: false }),
|
|
294
738
|
label: normalizeString(rawObservability.label, DEFAULT_RUNTIME_CONFIG.observability.label, { allowEmpty: true }),
|
|
295
739
|
environment: normalizeString(rawObservability.environment, DEFAULT_RUNTIME_CONFIG.observability.environment, { allowEmpty: false }),
|
|
@@ -298,13 +742,18 @@ function normalizeRuntimeConfig(patch) {
|
|
|
298
742
|
},
|
|
299
743
|
sessionCompaction: {
|
|
300
744
|
enabled: normalizeBoolean(rawSessionCompaction.enabled, DEFAULT_RUNTIME_CONFIG.sessionCompaction.enabled),
|
|
745
|
+
tokenBudget,
|
|
746
|
+
budgetRatio,
|
|
301
747
|
threshold,
|
|
302
748
|
keepRecent,
|
|
303
749
|
summaryMaxChars: normalizeInteger(rawSessionCompaction.summaryMaxChars, DEFAULT_RUNTIME_CONFIG.sessionCompaction.summaryMaxChars, { min: 1_000 }),
|
|
304
750
|
preCompactionMemoryFlush: {
|
|
305
|
-
enabled: normalizeBoolean(rawPreFlush.enabled, DEFAULT_RUNTIME_CONFIG.sessionCompaction.preCompactionMemoryFlush
|
|
306
|
-
|
|
307
|
-
|
|
751
|
+
enabled: normalizeBoolean(rawPreFlush.enabled, DEFAULT_RUNTIME_CONFIG.sessionCompaction.preCompactionMemoryFlush
|
|
752
|
+
.enabled),
|
|
753
|
+
maxMessages: normalizeInteger(rawPreFlush.maxMessages, DEFAULT_RUNTIME_CONFIG.sessionCompaction.preCompactionMemoryFlush
|
|
754
|
+
.maxMessages, { min: 8 }),
|
|
755
|
+
maxChars: normalizeInteger(rawPreFlush.maxChars, DEFAULT_RUNTIME_CONFIG.sessionCompaction.preCompactionMemoryFlush
|
|
756
|
+
.maxChars, { min: 4_000 }),
|
|
308
757
|
},
|
|
309
758
|
},
|
|
310
759
|
promptHooks: {
|
|
@@ -337,6 +786,9 @@ function normalizeRuntimeConfig(patch) {
|
|
|
337
786
|
maxIterations: normalizeInteger(rawRalph.maxIterations, DEFAULT_RUNTIME_CONFIG.proactive.ralph.maxIterations, { min: -1, max: 64 }),
|
|
338
787
|
},
|
|
339
788
|
},
|
|
789
|
+
scheduler: {
|
|
790
|
+
jobs: normalizeSchedulerJobList(rawScheduler.jobs, DEFAULT_RUNTIME_CONFIG.scheduler.jobs),
|
|
791
|
+
},
|
|
340
792
|
};
|
|
341
793
|
}
|
|
342
794
|
function loadConfigPatchFromDisk() {
|
|
@@ -397,7 +849,7 @@ function scheduleWatcherRestart(reason) {
|
|
|
397
849
|
return;
|
|
398
850
|
}
|
|
399
851
|
watcherRetryAttempt += 1;
|
|
400
|
-
const delay = Math.min(WATCHER_RETRY_BASE_DELAY_MS *
|
|
852
|
+
const delay = Math.min(WATCHER_RETRY_BASE_DELAY_MS * 2 ** (watcherRetryAttempt - 1), WATCHER_RETRY_MAX_DELAY_MS);
|
|
401
853
|
console.warn(`[runtime-config] watcher restart in ${delay}ms (attempt ${watcherRetryAttempt}/${WATCHER_RETRY_MAX_ATTEMPTS})`);
|
|
402
854
|
watcherRestartTimer = setTimeout(() => {
|
|
403
855
|
watcherRestartTimer = null;
|
|
@@ -521,13 +973,15 @@ export function updateRuntimeConfig(mutator) {
|
|
|
521
973
|
return saveRuntimeConfig(draft);
|
|
522
974
|
}
|
|
523
975
|
export function isSecurityTrustAccepted(config = currentConfig) {
|
|
524
|
-
return Boolean(config.security.trustModelAccepted
|
|
525
|
-
|
|
526
|
-
|
|
976
|
+
return Boolean(config.security.trustModelAccepted &&
|
|
977
|
+
config.security.trustModelAcceptedAt &&
|
|
978
|
+
config.security.trustModelVersion === SECURITY_POLICY_VERSION);
|
|
527
979
|
}
|
|
528
980
|
export function acceptSecurityTrustModel(params) {
|
|
529
981
|
const acceptedAt = normalizeString(params?.acceptedAt, new Date().toISOString(), { allowEmpty: false });
|
|
530
|
-
const acceptedBy = normalizeString(params?.acceptedBy ?? '', '', {
|
|
982
|
+
const acceptedBy = normalizeString(params?.acceptedBy ?? '', '', {
|
|
983
|
+
allowEmpty: true,
|
|
984
|
+
});
|
|
531
985
|
const policyVersion = normalizeString(params?.policyVersion, SECURITY_POLICY_VERSION, { allowEmpty: false });
|
|
532
986
|
return updateRuntimeConfig((draft) => {
|
|
533
987
|
draft.security.trustModelAccepted = true;
|