@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/src/runtime-config.ts
CHANGED
|
@@ -1,13 +1,28 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
3
|
|
|
4
4
|
export const CONFIG_FILE_NAME = 'config.json';
|
|
5
|
-
export const CONFIG_VERSION =
|
|
5
|
+
export const CONFIG_VERSION = 5;
|
|
6
6
|
export const SECURITY_POLICY_VERSION = '2026-02-28';
|
|
7
7
|
|
|
8
|
-
const KNOWN_LOG_LEVELS = new Set([
|
|
8
|
+
const KNOWN_LOG_LEVELS = new Set([
|
|
9
|
+
'fatal',
|
|
10
|
+
'error',
|
|
11
|
+
'warn',
|
|
12
|
+
'info',
|
|
13
|
+
'debug',
|
|
14
|
+
'trace',
|
|
15
|
+
'silent',
|
|
16
|
+
]);
|
|
9
17
|
|
|
10
|
-
type LogLevel =
|
|
18
|
+
type LogLevel =
|
|
19
|
+
| 'fatal'
|
|
20
|
+
| 'error'
|
|
21
|
+
| 'warn'
|
|
22
|
+
| 'info'
|
|
23
|
+
| 'debug'
|
|
24
|
+
| 'trace'
|
|
25
|
+
| 'silent';
|
|
11
26
|
|
|
12
27
|
type DeepPartial<T> = {
|
|
13
28
|
[K in keyof T]?: T[K] extends Array<infer U>
|
|
@@ -24,6 +39,95 @@ export interface RuntimeSecurityConfig {
|
|
|
24
39
|
trustModelAcceptedBy: string;
|
|
25
40
|
}
|
|
26
41
|
|
|
42
|
+
export type DiscordGroupPolicy = 'open' | 'allowlist' | 'disabled';
|
|
43
|
+
export type DiscordChannelMode = 'off' | 'mention' | 'free';
|
|
44
|
+
export type DiscordTypingMode = 'instant' | 'thinking' | 'streaming' | 'never';
|
|
45
|
+
export type DiscordHumanDelayMode = 'off' | 'natural' | 'custom';
|
|
46
|
+
export type DiscordAckReactionScope =
|
|
47
|
+
| 'all'
|
|
48
|
+
| 'group-mentions'
|
|
49
|
+
| 'direct'
|
|
50
|
+
| 'off';
|
|
51
|
+
export type DiscordPresenceActivityType =
|
|
52
|
+
| 'playing'
|
|
53
|
+
| 'watching'
|
|
54
|
+
| 'listening'
|
|
55
|
+
| 'competing'
|
|
56
|
+
| 'custom';
|
|
57
|
+
export type SchedulerScheduleKind = 'at' | 'every' | 'cron';
|
|
58
|
+
export type SchedulerActionKind = 'agent_turn' | 'system_event';
|
|
59
|
+
export type SchedulerDeliveryKind = 'channel' | 'last-channel' | 'webhook';
|
|
60
|
+
|
|
61
|
+
export interface RuntimeDiscordHumanDelayConfig {
|
|
62
|
+
mode: DiscordHumanDelayMode;
|
|
63
|
+
minMs: number;
|
|
64
|
+
maxMs: number;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface RuntimeDiscordPresenceConfig {
|
|
68
|
+
enabled: boolean;
|
|
69
|
+
intervalMs: number;
|
|
70
|
+
healthyText: string;
|
|
71
|
+
degradedText: string;
|
|
72
|
+
exhaustedText: string;
|
|
73
|
+
activityType: DiscordPresenceActivityType;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface RuntimeDiscordLifecycleReactionsConfig {
|
|
77
|
+
enabled: boolean;
|
|
78
|
+
removeOnComplete: boolean;
|
|
79
|
+
phases: {
|
|
80
|
+
queued: string;
|
|
81
|
+
thinking: string;
|
|
82
|
+
toolUse: string;
|
|
83
|
+
streaming: string;
|
|
84
|
+
done: string;
|
|
85
|
+
error: string;
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface RuntimeDiscordChannelConfig {
|
|
90
|
+
mode: DiscordChannelMode;
|
|
91
|
+
typingMode?: DiscordTypingMode;
|
|
92
|
+
debounceMs?: number;
|
|
93
|
+
ackReaction?: string;
|
|
94
|
+
ackReactionScope?: DiscordAckReactionScope;
|
|
95
|
+
removeAckAfterReply?: boolean;
|
|
96
|
+
humanDelay?: RuntimeDiscordHumanDelayConfig;
|
|
97
|
+
rateLimitPerUser?: number;
|
|
98
|
+
suppressPatterns?: string[];
|
|
99
|
+
maxConcurrentPerChannel?: number;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface RuntimeDiscordGuildConfig {
|
|
103
|
+
defaultMode: DiscordChannelMode;
|
|
104
|
+
channels: Record<string, RuntimeDiscordChannelConfig>;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export interface RuntimeSchedulerJob {
|
|
108
|
+
id: string;
|
|
109
|
+
name?: string;
|
|
110
|
+
description?: string;
|
|
111
|
+
schedule: {
|
|
112
|
+
kind: SchedulerScheduleKind;
|
|
113
|
+
at: string | null;
|
|
114
|
+
everyMs: number | null;
|
|
115
|
+
expr: string | null;
|
|
116
|
+
tz: string;
|
|
117
|
+
};
|
|
118
|
+
action: {
|
|
119
|
+
kind: SchedulerActionKind;
|
|
120
|
+
message: string;
|
|
121
|
+
};
|
|
122
|
+
delivery: {
|
|
123
|
+
kind: SchedulerDeliveryKind;
|
|
124
|
+
channel: string;
|
|
125
|
+
to: string;
|
|
126
|
+
webhookUrl: string;
|
|
127
|
+
};
|
|
128
|
+
enabled: boolean;
|
|
129
|
+
}
|
|
130
|
+
|
|
27
131
|
export interface RuntimeConfig {
|
|
28
132
|
version: number;
|
|
29
133
|
security: RuntimeSecurityConfig;
|
|
@@ -37,6 +141,21 @@ export interface RuntimeConfig {
|
|
|
37
141
|
respondToAllMessages: boolean;
|
|
38
142
|
commandsOnly: boolean;
|
|
39
143
|
commandUserId: string;
|
|
144
|
+
groupPolicy: DiscordGroupPolicy;
|
|
145
|
+
freeResponseChannels: string[];
|
|
146
|
+
humanDelay: RuntimeDiscordHumanDelayConfig;
|
|
147
|
+
typingMode: DiscordTypingMode;
|
|
148
|
+
presence: RuntimeDiscordPresenceConfig;
|
|
149
|
+
lifecycleReactions: RuntimeDiscordLifecycleReactionsConfig;
|
|
150
|
+
ackReaction: string;
|
|
151
|
+
ackReactionScope: DiscordAckReactionScope;
|
|
152
|
+
removeAckAfterReply: boolean;
|
|
153
|
+
debounceMs: number;
|
|
154
|
+
rateLimitPerUser: number;
|
|
155
|
+
rateLimitExemptRoles: string[];
|
|
156
|
+
suppressPatterns: string[];
|
|
157
|
+
maxConcurrentPerChannel: number;
|
|
158
|
+
guilds: Record<string, RuntimeDiscordGuildConfig>;
|
|
40
159
|
};
|
|
41
160
|
hybridai: {
|
|
42
161
|
baseUrl: string;
|
|
@@ -59,6 +178,10 @@ export interface RuntimeConfig {
|
|
|
59
178
|
intervalMs: number;
|
|
60
179
|
channel: string;
|
|
61
180
|
};
|
|
181
|
+
memory: {
|
|
182
|
+
decayRate: number;
|
|
183
|
+
consolidationIntervalHours: number;
|
|
184
|
+
};
|
|
62
185
|
ops: {
|
|
63
186
|
healthHost: string;
|
|
64
187
|
healthPort: number;
|
|
@@ -82,6 +205,8 @@ export interface RuntimeConfig {
|
|
|
82
205
|
};
|
|
83
206
|
sessionCompaction: {
|
|
84
207
|
enabled: boolean;
|
|
208
|
+
tokenBudget: number;
|
|
209
|
+
budgetRatio: number;
|
|
85
210
|
threshold: number;
|
|
86
211
|
keepRecent: number;
|
|
87
212
|
summaryMaxChars: number;
|
|
@@ -121,9 +246,15 @@ export interface RuntimeConfig {
|
|
|
121
246
|
maxIterations: number;
|
|
122
247
|
};
|
|
123
248
|
};
|
|
249
|
+
scheduler: {
|
|
250
|
+
jobs: RuntimeSchedulerJob[];
|
|
251
|
+
};
|
|
124
252
|
}
|
|
125
253
|
|
|
126
|
-
export type RuntimeConfigChangeListener = (
|
|
254
|
+
export type RuntimeConfigChangeListener = (
|
|
255
|
+
next: RuntimeConfig,
|
|
256
|
+
prev: RuntimeConfig,
|
|
257
|
+
) => void;
|
|
127
258
|
|
|
128
259
|
const DEFAULT_RUNTIME_CONFIG: RuntimeConfig = {
|
|
129
260
|
version: CONFIG_VERSION,
|
|
@@ -143,6 +274,43 @@ const DEFAULT_RUNTIME_CONFIG: RuntimeConfig = {
|
|
|
143
274
|
respondToAllMessages: false,
|
|
144
275
|
commandsOnly: false,
|
|
145
276
|
commandUserId: '',
|
|
277
|
+
groupPolicy: 'open',
|
|
278
|
+
freeResponseChannels: [],
|
|
279
|
+
humanDelay: {
|
|
280
|
+
mode: 'natural',
|
|
281
|
+
minMs: 800,
|
|
282
|
+
maxMs: 2_500,
|
|
283
|
+
},
|
|
284
|
+
typingMode: 'thinking',
|
|
285
|
+
presence: {
|
|
286
|
+
enabled: true,
|
|
287
|
+
intervalMs: 30_000,
|
|
288
|
+
healthyText: 'Watching the channels',
|
|
289
|
+
degradedText: 'Thinking slowly...',
|
|
290
|
+
exhaustedText: 'Taking a break',
|
|
291
|
+
activityType: 'watching',
|
|
292
|
+
},
|
|
293
|
+
lifecycleReactions: {
|
|
294
|
+
enabled: true,
|
|
295
|
+
removeOnComplete: true,
|
|
296
|
+
phases: {
|
|
297
|
+
queued: '⏳',
|
|
298
|
+
thinking: '🤔',
|
|
299
|
+
toolUse: '⚙️',
|
|
300
|
+
streaming: '✍️',
|
|
301
|
+
done: '✅',
|
|
302
|
+
error: '❌',
|
|
303
|
+
},
|
|
304
|
+
},
|
|
305
|
+
ackReaction: '👀',
|
|
306
|
+
ackReactionScope: 'group-mentions',
|
|
307
|
+
removeAckAfterReply: true,
|
|
308
|
+
debounceMs: 2_500,
|
|
309
|
+
rateLimitPerUser: 0,
|
|
310
|
+
rateLimitExemptRoles: [],
|
|
311
|
+
suppressPatterns: ['/stop', '/pause', 'brb', 'afk'],
|
|
312
|
+
maxConcurrentPerChannel: 2,
|
|
313
|
+
guilds: {},
|
|
146
314
|
},
|
|
147
315
|
hybridai: {
|
|
148
316
|
baseUrl: 'https://hybridai.one',
|
|
@@ -165,6 +333,10 @@ const DEFAULT_RUNTIME_CONFIG: RuntimeConfig = {
|
|
|
165
333
|
intervalMs: 1_800_000,
|
|
166
334
|
channel: '',
|
|
167
335
|
},
|
|
336
|
+
memory: {
|
|
337
|
+
decayRate: 0.1,
|
|
338
|
+
consolidationIntervalHours: 24,
|
|
339
|
+
},
|
|
168
340
|
ops: {
|
|
169
341
|
healthHost: '127.0.0.1',
|
|
170
342
|
healthPort: 9090,
|
|
@@ -188,7 +360,9 @@ const DEFAULT_RUNTIME_CONFIG: RuntimeConfig = {
|
|
|
188
360
|
},
|
|
189
361
|
sessionCompaction: {
|
|
190
362
|
enabled: true,
|
|
191
|
-
|
|
363
|
+
tokenBudget: 100_000,
|
|
364
|
+
budgetRatio: 0.7,
|
|
365
|
+
threshold: 200,
|
|
192
366
|
keepRecent: 40,
|
|
193
367
|
summaryMaxChars: 8_000,
|
|
194
368
|
preCompactionMemoryFlush: {
|
|
@@ -227,6 +401,9 @@ const DEFAULT_RUNTIME_CONFIG: RuntimeConfig = {
|
|
|
227
401
|
maxIterations: 0,
|
|
228
402
|
},
|
|
229
403
|
},
|
|
404
|
+
scheduler: {
|
|
405
|
+
jobs: [],
|
|
406
|
+
},
|
|
230
407
|
};
|
|
231
408
|
|
|
232
409
|
const CONFIG_PATH = path.join(process.cwd(), CONFIG_FILE_NAME);
|
|
@@ -292,6 +469,26 @@ function normalizeInteger(
|
|
|
292
469
|
return parsed;
|
|
293
470
|
}
|
|
294
471
|
|
|
472
|
+
function normalizeNumber(
|
|
473
|
+
value: unknown,
|
|
474
|
+
fallback: number,
|
|
475
|
+
opts?: { min?: number; max?: number },
|
|
476
|
+
): number {
|
|
477
|
+
let parsed: number;
|
|
478
|
+
if (typeof value === 'number') {
|
|
479
|
+
parsed = value;
|
|
480
|
+
} else if (typeof value === 'string' && value.trim()) {
|
|
481
|
+
parsed = Number.parseFloat(value);
|
|
482
|
+
} else {
|
|
483
|
+
parsed = fallback;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
if (!Number.isFinite(parsed)) parsed = fallback;
|
|
487
|
+
if (opts?.min != null && parsed < opts.min) parsed = opts.min;
|
|
488
|
+
if (opts?.max != null && parsed > opts.max) parsed = opts.max;
|
|
489
|
+
return parsed;
|
|
490
|
+
}
|
|
491
|
+
|
|
295
492
|
function normalizeStringArray(value: unknown, fallback: string[]): string[] {
|
|
296
493
|
if (Array.isArray(value)) {
|
|
297
494
|
const normalized = value
|
|
@@ -312,8 +509,483 @@ function normalizeStringArray(value: unknown, fallback: string[]): string[] {
|
|
|
312
509
|
return fallback;
|
|
313
510
|
}
|
|
314
511
|
|
|
512
|
+
function normalizeDiscordGroupPolicy(
|
|
513
|
+
value: unknown,
|
|
514
|
+
fallback: DiscordGroupPolicy,
|
|
515
|
+
): DiscordGroupPolicy {
|
|
516
|
+
if (typeof value !== 'string') return fallback;
|
|
517
|
+
const normalized = value.trim().toLowerCase();
|
|
518
|
+
if (
|
|
519
|
+
normalized === 'open' ||
|
|
520
|
+
normalized === 'allowlist' ||
|
|
521
|
+
normalized === 'disabled'
|
|
522
|
+
) {
|
|
523
|
+
return normalized;
|
|
524
|
+
}
|
|
525
|
+
return fallback;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
function normalizeDiscordChannelMode(
|
|
529
|
+
value: unknown,
|
|
530
|
+
fallback: DiscordChannelMode,
|
|
531
|
+
): DiscordChannelMode {
|
|
532
|
+
if (typeof value !== 'string') return fallback;
|
|
533
|
+
const normalized = value.trim().toLowerCase();
|
|
534
|
+
if (normalized === 'off' || normalized === 'mention' || normalized === 'free')
|
|
535
|
+
return normalized;
|
|
536
|
+
if (normalized === 'free-response' || normalized === 'free_response')
|
|
537
|
+
return 'free';
|
|
538
|
+
return fallback;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
function normalizeDiscordTypingMode(
|
|
542
|
+
value: unknown,
|
|
543
|
+
fallback: DiscordTypingMode,
|
|
544
|
+
): DiscordTypingMode {
|
|
545
|
+
if (typeof value !== 'string') return fallback;
|
|
546
|
+
const normalized = value.trim().toLowerCase();
|
|
547
|
+
if (
|
|
548
|
+
normalized === 'instant' ||
|
|
549
|
+
normalized === 'thinking' ||
|
|
550
|
+
normalized === 'streaming' ||
|
|
551
|
+
normalized === 'never'
|
|
552
|
+
) {
|
|
553
|
+
return normalized;
|
|
554
|
+
}
|
|
555
|
+
return fallback;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
function normalizeDiscordHumanDelayMode(
|
|
559
|
+
value: unknown,
|
|
560
|
+
fallback: DiscordHumanDelayMode,
|
|
561
|
+
): DiscordHumanDelayMode {
|
|
562
|
+
if (typeof value !== 'string') return fallback;
|
|
563
|
+
const normalized = value.trim().toLowerCase();
|
|
564
|
+
if (
|
|
565
|
+
normalized === 'off' ||
|
|
566
|
+
normalized === 'natural' ||
|
|
567
|
+
normalized === 'custom'
|
|
568
|
+
) {
|
|
569
|
+
return normalized;
|
|
570
|
+
}
|
|
571
|
+
return fallback;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
function normalizeDiscordAckReactionScope(
|
|
575
|
+
value: unknown,
|
|
576
|
+
fallback: DiscordAckReactionScope,
|
|
577
|
+
): DiscordAckReactionScope {
|
|
578
|
+
if (typeof value !== 'string') return fallback;
|
|
579
|
+
const normalized = value.trim().toLowerCase();
|
|
580
|
+
if (
|
|
581
|
+
normalized === 'all' ||
|
|
582
|
+
normalized === 'group-mentions' ||
|
|
583
|
+
normalized === 'direct' ||
|
|
584
|
+
normalized === 'off'
|
|
585
|
+
) {
|
|
586
|
+
return normalized;
|
|
587
|
+
}
|
|
588
|
+
return fallback;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
function normalizeDiscordPresenceActivityType(
|
|
592
|
+
value: unknown,
|
|
593
|
+
fallback: DiscordPresenceActivityType,
|
|
594
|
+
): DiscordPresenceActivityType {
|
|
595
|
+
if (typeof value !== 'string') return fallback;
|
|
596
|
+
const normalized = value.trim().toLowerCase();
|
|
597
|
+
if (
|
|
598
|
+
normalized === 'playing' ||
|
|
599
|
+
normalized === 'watching' ||
|
|
600
|
+
normalized === 'listening' ||
|
|
601
|
+
normalized === 'competing' ||
|
|
602
|
+
normalized === 'custom'
|
|
603
|
+
) {
|
|
604
|
+
return normalized;
|
|
605
|
+
}
|
|
606
|
+
return fallback;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
function normalizeDiscordHumanDelayConfig(
|
|
610
|
+
value: unknown,
|
|
611
|
+
fallback: RuntimeDiscordHumanDelayConfig,
|
|
612
|
+
): RuntimeDiscordHumanDelayConfig {
|
|
613
|
+
const raw = isRecord(value) ? value : {};
|
|
614
|
+
const mode = normalizeDiscordHumanDelayMode(raw.mode, fallback.mode);
|
|
615
|
+
const minMs = normalizeInteger(raw.minMs, fallback.minMs, {
|
|
616
|
+
min: 0,
|
|
617
|
+
max: 120_000,
|
|
618
|
+
});
|
|
619
|
+
const maxMsRaw = normalizeInteger(raw.maxMs, fallback.maxMs, {
|
|
620
|
+
min: 0,
|
|
621
|
+
max: 120_000,
|
|
622
|
+
});
|
|
623
|
+
const maxMs = Math.max(minMs, maxMsRaw);
|
|
624
|
+
return { mode, minMs, maxMs };
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
function normalizeDiscordPresenceConfig(
|
|
628
|
+
value: unknown,
|
|
629
|
+
fallback: RuntimeDiscordPresenceConfig,
|
|
630
|
+
): RuntimeDiscordPresenceConfig {
|
|
631
|
+
const raw = isRecord(value) ? value : {};
|
|
632
|
+
return {
|
|
633
|
+
enabled: normalizeBoolean(raw.enabled, fallback.enabled),
|
|
634
|
+
intervalMs: normalizeInteger(raw.intervalMs, fallback.intervalMs, {
|
|
635
|
+
min: 5_000,
|
|
636
|
+
max: 300_000,
|
|
637
|
+
}),
|
|
638
|
+
healthyText: normalizeString(raw.healthyText, fallback.healthyText, {
|
|
639
|
+
allowEmpty: false,
|
|
640
|
+
}),
|
|
641
|
+
degradedText: normalizeString(raw.degradedText, fallback.degradedText, {
|
|
642
|
+
allowEmpty: false,
|
|
643
|
+
}),
|
|
644
|
+
exhaustedText: normalizeString(raw.exhaustedText, fallback.exhaustedText, {
|
|
645
|
+
allowEmpty: false,
|
|
646
|
+
}),
|
|
647
|
+
activityType: normalizeDiscordPresenceActivityType(
|
|
648
|
+
raw.activityType,
|
|
649
|
+
fallback.activityType,
|
|
650
|
+
),
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
function normalizeDiscordLifecycleReactionsConfig(
|
|
655
|
+
value: unknown,
|
|
656
|
+
fallback: RuntimeDiscordLifecycleReactionsConfig,
|
|
657
|
+
): RuntimeDiscordLifecycleReactionsConfig {
|
|
658
|
+
const raw = isRecord(value) ? value : {};
|
|
659
|
+
const rawPhases = isRecord(raw.phases) ? raw.phases : {};
|
|
660
|
+
return {
|
|
661
|
+
enabled: normalizeBoolean(raw.enabled, fallback.enabled),
|
|
662
|
+
removeOnComplete: normalizeBoolean(
|
|
663
|
+
raw.removeOnComplete,
|
|
664
|
+
fallback.removeOnComplete,
|
|
665
|
+
),
|
|
666
|
+
phases: {
|
|
667
|
+
queued: normalizeString(rawPhases.queued, fallback.phases.queued, {
|
|
668
|
+
allowEmpty: false,
|
|
669
|
+
}),
|
|
670
|
+
thinking: normalizeString(rawPhases.thinking, fallback.phases.thinking, {
|
|
671
|
+
allowEmpty: false,
|
|
672
|
+
}),
|
|
673
|
+
toolUse: normalizeString(rawPhases.toolUse, fallback.phases.toolUse, {
|
|
674
|
+
allowEmpty: false,
|
|
675
|
+
}),
|
|
676
|
+
streaming: normalizeString(
|
|
677
|
+
rawPhases.streaming,
|
|
678
|
+
fallback.phases.streaming,
|
|
679
|
+
{ allowEmpty: false },
|
|
680
|
+
),
|
|
681
|
+
done: normalizeString(rawPhases.done, fallback.phases.done, {
|
|
682
|
+
allowEmpty: false,
|
|
683
|
+
}),
|
|
684
|
+
error: normalizeString(rawPhases.error, fallback.phases.error, {
|
|
685
|
+
allowEmpty: false,
|
|
686
|
+
}),
|
|
687
|
+
},
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
function normalizeDiscordChannelConfig(
|
|
692
|
+
value: unknown,
|
|
693
|
+
fallback: RuntimeDiscordChannelConfig,
|
|
694
|
+
defaultMode: DiscordChannelMode,
|
|
695
|
+
): RuntimeDiscordChannelConfig | null {
|
|
696
|
+
const channelFallback = {
|
|
697
|
+
...fallback,
|
|
698
|
+
mode: fallback.mode || defaultMode,
|
|
699
|
+
};
|
|
700
|
+
|
|
701
|
+
if (typeof value === 'string') {
|
|
702
|
+
return { mode: normalizeDiscordChannelMode(value, channelFallback.mode) };
|
|
703
|
+
}
|
|
704
|
+
if (!isRecord(value)) return null;
|
|
705
|
+
|
|
706
|
+
const channelConfig: RuntimeDiscordChannelConfig = {
|
|
707
|
+
mode: normalizeDiscordChannelMode(value.mode, channelFallback.mode),
|
|
708
|
+
};
|
|
709
|
+
|
|
710
|
+
if (
|
|
711
|
+
value.typingMode !== undefined ||
|
|
712
|
+
channelFallback.typingMode !== undefined
|
|
713
|
+
) {
|
|
714
|
+
channelConfig.typingMode = normalizeDiscordTypingMode(
|
|
715
|
+
value.typingMode,
|
|
716
|
+
channelFallback.typingMode ?? DEFAULT_RUNTIME_CONFIG.discord.typingMode,
|
|
717
|
+
);
|
|
718
|
+
}
|
|
719
|
+
if (
|
|
720
|
+
value.debounceMs !== undefined ||
|
|
721
|
+
channelFallback.debounceMs !== undefined
|
|
722
|
+
) {
|
|
723
|
+
channelConfig.debounceMs = normalizeInteger(
|
|
724
|
+
value.debounceMs,
|
|
725
|
+
channelFallback.debounceMs ?? DEFAULT_RUNTIME_CONFIG.discord.debounceMs,
|
|
726
|
+
{ min: 0, max: 120_000 },
|
|
727
|
+
);
|
|
728
|
+
}
|
|
729
|
+
if (
|
|
730
|
+
value.ackReaction !== undefined ||
|
|
731
|
+
channelFallback.ackReaction !== undefined
|
|
732
|
+
) {
|
|
733
|
+
channelConfig.ackReaction = normalizeString(
|
|
734
|
+
value.ackReaction,
|
|
735
|
+
channelFallback.ackReaction ?? DEFAULT_RUNTIME_CONFIG.discord.ackReaction,
|
|
736
|
+
{ allowEmpty: false },
|
|
737
|
+
);
|
|
738
|
+
}
|
|
739
|
+
if (
|
|
740
|
+
value.ackReactionScope !== undefined ||
|
|
741
|
+
channelFallback.ackReactionScope !== undefined
|
|
742
|
+
) {
|
|
743
|
+
channelConfig.ackReactionScope = normalizeDiscordAckReactionScope(
|
|
744
|
+
value.ackReactionScope,
|
|
745
|
+
channelFallback.ackReactionScope ??
|
|
746
|
+
DEFAULT_RUNTIME_CONFIG.discord.ackReactionScope,
|
|
747
|
+
);
|
|
748
|
+
}
|
|
749
|
+
if (
|
|
750
|
+
value.removeAckAfterReply !== undefined ||
|
|
751
|
+
channelFallback.removeAckAfterReply !== undefined
|
|
752
|
+
) {
|
|
753
|
+
channelConfig.removeAckAfterReply = normalizeBoolean(
|
|
754
|
+
value.removeAckAfterReply,
|
|
755
|
+
channelFallback.removeAckAfterReply ??
|
|
756
|
+
DEFAULT_RUNTIME_CONFIG.discord.removeAckAfterReply,
|
|
757
|
+
);
|
|
758
|
+
}
|
|
759
|
+
if (
|
|
760
|
+
value.humanDelay !== undefined ||
|
|
761
|
+
channelFallback.humanDelay !== undefined
|
|
762
|
+
) {
|
|
763
|
+
channelConfig.humanDelay = normalizeDiscordHumanDelayConfig(
|
|
764
|
+
value.humanDelay,
|
|
765
|
+
channelFallback.humanDelay ?? DEFAULT_RUNTIME_CONFIG.discord.humanDelay,
|
|
766
|
+
);
|
|
767
|
+
}
|
|
768
|
+
if (
|
|
769
|
+
value.rateLimitPerUser !== undefined ||
|
|
770
|
+
channelFallback.rateLimitPerUser !== undefined
|
|
771
|
+
) {
|
|
772
|
+
channelConfig.rateLimitPerUser = normalizeInteger(
|
|
773
|
+
value.rateLimitPerUser,
|
|
774
|
+
channelFallback.rateLimitPerUser ??
|
|
775
|
+
DEFAULT_RUNTIME_CONFIG.discord.rateLimitPerUser,
|
|
776
|
+
{ min: 0, max: 300 },
|
|
777
|
+
);
|
|
778
|
+
}
|
|
779
|
+
if (
|
|
780
|
+
value.suppressPatterns !== undefined ||
|
|
781
|
+
channelFallback.suppressPatterns !== undefined
|
|
782
|
+
) {
|
|
783
|
+
channelConfig.suppressPatterns = normalizeStringArray(
|
|
784
|
+
value.suppressPatterns,
|
|
785
|
+
channelFallback.suppressPatterns ??
|
|
786
|
+
DEFAULT_RUNTIME_CONFIG.discord.suppressPatterns,
|
|
787
|
+
);
|
|
788
|
+
}
|
|
789
|
+
if (
|
|
790
|
+
value.maxConcurrentPerChannel !== undefined ||
|
|
791
|
+
channelFallback.maxConcurrentPerChannel !== undefined
|
|
792
|
+
) {
|
|
793
|
+
channelConfig.maxConcurrentPerChannel = normalizeInteger(
|
|
794
|
+
value.maxConcurrentPerChannel,
|
|
795
|
+
channelFallback.maxConcurrentPerChannel ??
|
|
796
|
+
DEFAULT_RUNTIME_CONFIG.discord.maxConcurrentPerChannel,
|
|
797
|
+
{ min: 1, max: 16 },
|
|
798
|
+
);
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
return channelConfig;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
function normalizeDiscordGuildConfig(
|
|
805
|
+
value: unknown,
|
|
806
|
+
fallback: RuntimeDiscordGuildConfig,
|
|
807
|
+
): RuntimeDiscordGuildConfig {
|
|
808
|
+
if (!isRecord(value)) return fallback;
|
|
809
|
+
const defaultMode = normalizeDiscordChannelMode(
|
|
810
|
+
value.defaultMode,
|
|
811
|
+
fallback.defaultMode,
|
|
812
|
+
);
|
|
813
|
+
const rawChannels = isRecord(value.channels) ? value.channels : {};
|
|
814
|
+
const channels: Record<string, RuntimeDiscordChannelConfig> = {};
|
|
815
|
+
for (const [rawChannelId, rawChannelConfig] of Object.entries(rawChannels)) {
|
|
816
|
+
const channelId = rawChannelId.trim();
|
|
817
|
+
if (!channelId) continue;
|
|
818
|
+
const fallbackChannel = fallback.channels[channelId] ?? {
|
|
819
|
+
mode: defaultMode,
|
|
820
|
+
};
|
|
821
|
+
const channelConfig = normalizeDiscordChannelConfig(
|
|
822
|
+
rawChannelConfig,
|
|
823
|
+
fallbackChannel,
|
|
824
|
+
defaultMode,
|
|
825
|
+
);
|
|
826
|
+
if (!channelConfig) continue;
|
|
827
|
+
channels[channelId] = channelConfig;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
return { defaultMode, channels };
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
function normalizeDiscordGuildMap(
|
|
834
|
+
value: unknown,
|
|
835
|
+
fallback: Record<string, RuntimeDiscordGuildConfig>,
|
|
836
|
+
): Record<string, RuntimeDiscordGuildConfig> {
|
|
837
|
+
if (!isRecord(value)) return fallback;
|
|
838
|
+
const guilds: Record<string, RuntimeDiscordGuildConfig> = {};
|
|
839
|
+
for (const [rawGuildId, rawGuildConfig] of Object.entries(value)) {
|
|
840
|
+
const guildId = rawGuildId.trim();
|
|
841
|
+
if (!guildId) continue;
|
|
842
|
+
const fallbackGuild = fallback[guildId] ?? {
|
|
843
|
+
defaultMode: 'mention',
|
|
844
|
+
channels: {},
|
|
845
|
+
};
|
|
846
|
+
guilds[guildId] = normalizeDiscordGuildConfig(
|
|
847
|
+
rawGuildConfig,
|
|
848
|
+
fallbackGuild,
|
|
849
|
+
);
|
|
850
|
+
}
|
|
851
|
+
return guilds;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
function normalizeSchedulerScheduleKind(
|
|
855
|
+
value: unknown,
|
|
856
|
+
fallback: SchedulerScheduleKind,
|
|
857
|
+
): SchedulerScheduleKind {
|
|
858
|
+
if (typeof value !== 'string') return fallback;
|
|
859
|
+
const normalized = value.trim().toLowerCase();
|
|
860
|
+
if (normalized === 'at' || normalized === 'every' || normalized === 'cron')
|
|
861
|
+
return normalized;
|
|
862
|
+
return fallback;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
function normalizeSchedulerActionKind(
|
|
866
|
+
value: unknown,
|
|
867
|
+
fallback: SchedulerActionKind,
|
|
868
|
+
): SchedulerActionKind {
|
|
869
|
+
if (typeof value !== 'string') return fallback;
|
|
870
|
+
const normalized = value.trim().toLowerCase();
|
|
871
|
+
if (normalized === 'agent_turn' || normalized === 'system_event')
|
|
872
|
+
return normalized;
|
|
873
|
+
return fallback;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
function normalizeSchedulerDeliveryKind(
|
|
877
|
+
value: unknown,
|
|
878
|
+
fallback: SchedulerDeliveryKind,
|
|
879
|
+
): SchedulerDeliveryKind {
|
|
880
|
+
if (typeof value !== 'string') return fallback;
|
|
881
|
+
const normalized = value.trim().toLowerCase();
|
|
882
|
+
if (
|
|
883
|
+
normalized === 'channel' ||
|
|
884
|
+
normalized === 'last-channel' ||
|
|
885
|
+
normalized === 'webhook'
|
|
886
|
+
)
|
|
887
|
+
return normalized;
|
|
888
|
+
return fallback;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
function normalizeSchedulerJobList(
|
|
892
|
+
value: unknown,
|
|
893
|
+
fallback: RuntimeSchedulerJob[],
|
|
894
|
+
): RuntimeSchedulerJob[] {
|
|
895
|
+
if (!Array.isArray(value)) return fallback;
|
|
896
|
+
const jobs: RuntimeSchedulerJob[] = [];
|
|
897
|
+
for (const item of value) {
|
|
898
|
+
if (!isRecord(item)) continue;
|
|
899
|
+
const jobId = normalizeString(item.id, '', { allowEmpty: false });
|
|
900
|
+
if (!jobId) continue;
|
|
901
|
+
|
|
902
|
+
const rawSchedule = isRecord(item.schedule) ? item.schedule : {};
|
|
903
|
+
const rawAction = isRecord(item.action) ? item.action : {};
|
|
904
|
+
const rawDelivery = isRecord(item.delivery) ? item.delivery : {};
|
|
905
|
+
|
|
906
|
+
const scheduleKind = normalizeSchedulerScheduleKind(
|
|
907
|
+
rawSchedule.kind,
|
|
908
|
+
'cron',
|
|
909
|
+
);
|
|
910
|
+
const everyMs =
|
|
911
|
+
scheduleKind === 'every'
|
|
912
|
+
? normalizeInteger(rawSchedule.everyMs, 60_000, {
|
|
913
|
+
min: 10_000,
|
|
914
|
+
max: 86_400_000,
|
|
915
|
+
})
|
|
916
|
+
: null;
|
|
917
|
+
const atRaw =
|
|
918
|
+
scheduleKind === 'at'
|
|
919
|
+
? normalizeString(rawSchedule.at, '', { allowEmpty: false })
|
|
920
|
+
: '';
|
|
921
|
+
const atIso =
|
|
922
|
+
scheduleKind === 'at'
|
|
923
|
+
? (() => {
|
|
924
|
+
const parsed = new Date(atRaw);
|
|
925
|
+
return Number.isNaN(parsed.getTime()) ? null : parsed.toISOString();
|
|
926
|
+
})()
|
|
927
|
+
: null;
|
|
928
|
+
const expr =
|
|
929
|
+
scheduleKind === 'cron'
|
|
930
|
+
? normalizeString(rawSchedule.expr, '', { allowEmpty: false })
|
|
931
|
+
: '';
|
|
932
|
+
if (scheduleKind === 'at' && !atIso) continue;
|
|
933
|
+
if (scheduleKind === 'cron' && !expr) continue;
|
|
934
|
+
|
|
935
|
+
const deliveryKind = normalizeSchedulerDeliveryKind(
|
|
936
|
+
rawDelivery.kind,
|
|
937
|
+
'channel',
|
|
938
|
+
);
|
|
939
|
+
const to = normalizeString(rawDelivery.to, '', { allowEmpty: true });
|
|
940
|
+
const webhookUrl = normalizeString(
|
|
941
|
+
rawDelivery.webhookUrl ?? rawDelivery.url,
|
|
942
|
+
'',
|
|
943
|
+
{ allowEmpty: true },
|
|
944
|
+
);
|
|
945
|
+
if (deliveryKind === 'channel' && !to) continue;
|
|
946
|
+
if (deliveryKind === 'webhook' && !webhookUrl) continue;
|
|
947
|
+
const actionMessage = normalizeString(rawAction.message, '', {
|
|
948
|
+
allowEmpty: false,
|
|
949
|
+
});
|
|
950
|
+
if (!actionMessage) continue;
|
|
951
|
+
const name = normalizeString(item.name, '', { allowEmpty: true });
|
|
952
|
+
const description = normalizeString(item.description, '', {
|
|
953
|
+
allowEmpty: true,
|
|
954
|
+
});
|
|
955
|
+
|
|
956
|
+
jobs.push({
|
|
957
|
+
id: jobId,
|
|
958
|
+
...(name ? { name } : {}),
|
|
959
|
+
...(description ? { description } : {}),
|
|
960
|
+
schedule: {
|
|
961
|
+
kind: scheduleKind,
|
|
962
|
+
at: atIso,
|
|
963
|
+
everyMs,
|
|
964
|
+
expr: scheduleKind === 'cron' ? expr : null,
|
|
965
|
+
tz: normalizeString(rawSchedule.tz, '', { allowEmpty: true }),
|
|
966
|
+
},
|
|
967
|
+
action: {
|
|
968
|
+
kind: normalizeSchedulerActionKind(rawAction.kind, 'agent_turn'),
|
|
969
|
+
message: actionMessage,
|
|
970
|
+
},
|
|
971
|
+
delivery: {
|
|
972
|
+
kind: deliveryKind,
|
|
973
|
+
channel: normalizeString(rawDelivery.channel, 'discord', {
|
|
974
|
+
allowEmpty: true,
|
|
975
|
+
}),
|
|
976
|
+
to,
|
|
977
|
+
webhookUrl,
|
|
978
|
+
},
|
|
979
|
+
enabled: normalizeBoolean(item.enabled, true),
|
|
980
|
+
});
|
|
981
|
+
}
|
|
982
|
+
return jobs;
|
|
983
|
+
}
|
|
984
|
+
|
|
315
985
|
function normalizeLogLevel(value: unknown, fallback: LogLevel): LogLevel {
|
|
316
|
-
const normalized = normalizeString(value, fallback, {
|
|
986
|
+
const normalized = normalizeString(value, fallback, {
|
|
987
|
+
allowEmpty: false,
|
|
988
|
+
}).toLowerCase();
|
|
317
989
|
if (KNOWN_LOG_LEVELS.has(normalized)) return normalized as LogLevel;
|
|
318
990
|
return fallback;
|
|
319
991
|
}
|
|
@@ -324,7 +996,10 @@ function normalizeBaseUrl(value: unknown, fallback: string): string {
|
|
|
324
996
|
}
|
|
325
997
|
|
|
326
998
|
function normalizeApiPath(value: unknown, fallback: string): string {
|
|
327
|
-
const normalized = normalizeString(value, fallback, {
|
|
999
|
+
const normalized = normalizeString(value, fallback, {
|
|
1000
|
+
allowEmpty: false,
|
|
1001
|
+
trim: true,
|
|
1002
|
+
});
|
|
328
1003
|
if (/^https?:\/\//i.test(normalized)) {
|
|
329
1004
|
return normalized.replace(/\/+$/, '');
|
|
330
1005
|
}
|
|
@@ -339,7 +1014,9 @@ function parseConfigPatch(payload: unknown): DeepPartial<RuntimeConfig> {
|
|
|
339
1014
|
return payload as DeepPartial<RuntimeConfig>;
|
|
340
1015
|
}
|
|
341
1016
|
|
|
342
|
-
function normalizeRuntimeConfig(
|
|
1017
|
+
function normalizeRuntimeConfig(
|
|
1018
|
+
patch?: DeepPartial<RuntimeConfig>,
|
|
1019
|
+
): RuntimeConfig {
|
|
343
1020
|
const raw = patch ?? {};
|
|
344
1021
|
|
|
345
1022
|
const rawSecurity = isRecord(raw.security) ? raw.security : {};
|
|
@@ -348,23 +1025,44 @@ function normalizeRuntimeConfig(patch?: DeepPartial<RuntimeConfig>): RuntimeConf
|
|
|
348
1025
|
const rawHybridAi = isRecord(raw.hybridai) ? raw.hybridai : {};
|
|
349
1026
|
const rawContainer = isRecord(raw.container) ? raw.container : {};
|
|
350
1027
|
const rawHeartbeat = isRecord(raw.heartbeat) ? raw.heartbeat : {};
|
|
1028
|
+
const rawMemory = isRecord(raw.memory) ? raw.memory : {};
|
|
351
1029
|
const rawOps = isRecord(raw.ops) ? raw.ops : {};
|
|
352
1030
|
const rawObservability = isRecord(raw.observability) ? raw.observability : {};
|
|
353
|
-
const rawSessionCompaction = isRecord(raw.sessionCompaction)
|
|
1031
|
+
const rawSessionCompaction = isRecord(raw.sessionCompaction)
|
|
1032
|
+
? raw.sessionCompaction
|
|
1033
|
+
: {};
|
|
354
1034
|
const rawPreFlush = isRecord(rawSessionCompaction.preCompactionMemoryFlush)
|
|
355
1035
|
? rawSessionCompaction.preCompactionMemoryFlush
|
|
356
1036
|
: {};
|
|
357
1037
|
const rawPromptHooks = isRecord(raw.promptHooks) ? raw.promptHooks : {};
|
|
358
1038
|
const rawProactive = isRecord(raw.proactive) ? raw.proactive : {};
|
|
359
|
-
const rawActiveHours = isRecord(rawProactive.activeHours)
|
|
360
|
-
|
|
361
|
-
|
|
1039
|
+
const rawActiveHours = isRecord(rawProactive.activeHours)
|
|
1040
|
+
? rawProactive.activeHours
|
|
1041
|
+
: {};
|
|
1042
|
+
const rawDelegation = isRecord(rawProactive.delegation)
|
|
1043
|
+
? rawProactive.delegation
|
|
1044
|
+
: {};
|
|
1045
|
+
const rawAutoRetry = isRecord(rawProactive.autoRetry)
|
|
1046
|
+
? rawProactive.autoRetry
|
|
1047
|
+
: {};
|
|
362
1048
|
const rawRalph = isRecord(rawProactive.ralph) ? rawProactive.ralph : {};
|
|
1049
|
+
const rawScheduler = isRecord(raw.scheduler) ? raw.scheduler : {};
|
|
363
1050
|
|
|
364
1051
|
const defaultOps = DEFAULT_RUNTIME_CONFIG.ops;
|
|
365
|
-
const healthPort = normalizeInteger(
|
|
366
|
-
|
|
367
|
-
|
|
1052
|
+
const healthPort = normalizeInteger(
|
|
1053
|
+
rawOps.healthPort,
|
|
1054
|
+
defaultOps.healthPort,
|
|
1055
|
+
{ min: 1, max: 65_535 },
|
|
1056
|
+
);
|
|
1057
|
+
const webApiToken = normalizeString(
|
|
1058
|
+
rawOps.webApiToken,
|
|
1059
|
+
defaultOps.webApiToken,
|
|
1060
|
+
{ allowEmpty: true },
|
|
1061
|
+
);
|
|
1062
|
+
const hybridBaseUrl = normalizeBaseUrl(
|
|
1063
|
+
rawHybridAi.baseUrl,
|
|
1064
|
+
DEFAULT_RUNTIME_CONFIG.hybridai.baseUrl,
|
|
1065
|
+
);
|
|
368
1066
|
const hybridDefaultChatbotId = normalizeString(
|
|
369
1067
|
rawHybridAi.defaultChatbotId,
|
|
370
1068
|
DEFAULT_RUNTIME_CONFIG.hybridai.defaultChatbotId,
|
|
@@ -376,6 +1074,16 @@ function normalizeRuntimeConfig(patch?: DeepPartial<RuntimeConfig>): RuntimeConf
|
|
|
376
1074
|
DEFAULT_RUNTIME_CONFIG.sessionCompaction.threshold,
|
|
377
1075
|
{ min: 20 },
|
|
378
1076
|
);
|
|
1077
|
+
const tokenBudget = normalizeInteger(
|
|
1078
|
+
rawSessionCompaction.tokenBudget,
|
|
1079
|
+
DEFAULT_RUNTIME_CONFIG.sessionCompaction.tokenBudget,
|
|
1080
|
+
{ min: 1_000 },
|
|
1081
|
+
);
|
|
1082
|
+
const budgetRatio = normalizeNumber(
|
|
1083
|
+
rawSessionCompaction.budgetRatio,
|
|
1084
|
+
DEFAULT_RUNTIME_CONFIG.sessionCompaction.budgetRatio,
|
|
1085
|
+
{ min: 0.05, max: 1 },
|
|
1086
|
+
);
|
|
379
1087
|
const keepRecentRaw = normalizeInteger(
|
|
380
1088
|
rawSessionCompaction.keepRecent,
|
|
381
1089
|
DEFAULT_RUNTIME_CONFIG.sessionCompaction.keepRecent,
|
|
@@ -383,21 +1091,46 @@ function normalizeRuntimeConfig(patch?: DeepPartial<RuntimeConfig>): RuntimeConf
|
|
|
383
1091
|
);
|
|
384
1092
|
const keepRecent = Math.min(keepRecentRaw, Math.max(1, threshold - 1));
|
|
385
1093
|
|
|
386
|
-
const modelList = normalizeStringArray(
|
|
1094
|
+
const modelList = normalizeStringArray(
|
|
1095
|
+
rawHybridAi.models,
|
|
1096
|
+
DEFAULT_RUNTIME_CONFIG.hybridai.models,
|
|
1097
|
+
);
|
|
387
1098
|
|
|
388
1099
|
return {
|
|
389
1100
|
version: CONFIG_VERSION,
|
|
390
1101
|
security: {
|
|
391
|
-
trustModelAccepted: normalizeBoolean(
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
1102
|
+
trustModelAccepted: normalizeBoolean(
|
|
1103
|
+
rawSecurity.trustModelAccepted,
|
|
1104
|
+
DEFAULT_RUNTIME_CONFIG.security.trustModelAccepted,
|
|
1105
|
+
),
|
|
1106
|
+
trustModelAcceptedAt: normalizeString(
|
|
1107
|
+
rawSecurity.trustModelAcceptedAt,
|
|
1108
|
+
DEFAULT_RUNTIME_CONFIG.security.trustModelAcceptedAt,
|
|
1109
|
+
{ allowEmpty: true },
|
|
1110
|
+
),
|
|
1111
|
+
trustModelVersion: normalizeString(
|
|
1112
|
+
rawSecurity.trustModelVersion,
|
|
1113
|
+
DEFAULT_RUNTIME_CONFIG.security.trustModelVersion,
|
|
1114
|
+
{ allowEmpty: true },
|
|
1115
|
+
),
|
|
1116
|
+
trustModelAcceptedBy: normalizeString(
|
|
1117
|
+
rawSecurity.trustModelAcceptedBy,
|
|
1118
|
+
DEFAULT_RUNTIME_CONFIG.security.trustModelAcceptedBy,
|
|
1119
|
+
{ allowEmpty: true },
|
|
1120
|
+
),
|
|
395
1121
|
},
|
|
396
1122
|
skills: {
|
|
397
|
-
extraDirs: normalizeStringArray(
|
|
1123
|
+
extraDirs: normalizeStringArray(
|
|
1124
|
+
rawSkills.extraDirs,
|
|
1125
|
+
DEFAULT_RUNTIME_CONFIG.skills.extraDirs,
|
|
1126
|
+
),
|
|
398
1127
|
},
|
|
399
1128
|
discord: {
|
|
400
|
-
prefix: normalizeString(
|
|
1129
|
+
prefix: normalizeString(
|
|
1130
|
+
rawDiscord.prefix,
|
|
1131
|
+
DEFAULT_RUNTIME_CONFIG.discord.prefix,
|
|
1132
|
+
{ allowEmpty: false },
|
|
1133
|
+
),
|
|
401
1134
|
guildMembersIntent: normalizeBoolean(
|
|
402
1135
|
rawDiscord.guildMembersIntent,
|
|
403
1136
|
DEFAULT_RUNTIME_CONFIG.discord.guildMembersIntent,
|
|
@@ -419,51 +1152,218 @@ function normalizeRuntimeConfig(patch?: DeepPartial<RuntimeConfig>): RuntimeConf
|
|
|
419
1152
|
DEFAULT_RUNTIME_CONFIG.discord.commandUserId,
|
|
420
1153
|
{ allowEmpty: true },
|
|
421
1154
|
),
|
|
1155
|
+
groupPolicy: normalizeDiscordGroupPolicy(
|
|
1156
|
+
rawDiscord.groupPolicy,
|
|
1157
|
+
DEFAULT_RUNTIME_CONFIG.discord.groupPolicy,
|
|
1158
|
+
),
|
|
1159
|
+
freeResponseChannels: normalizeStringArray(
|
|
1160
|
+
rawDiscord.freeResponseChannels,
|
|
1161
|
+
DEFAULT_RUNTIME_CONFIG.discord.freeResponseChannels,
|
|
1162
|
+
),
|
|
1163
|
+
humanDelay: normalizeDiscordHumanDelayConfig(
|
|
1164
|
+
rawDiscord.humanDelay,
|
|
1165
|
+
DEFAULT_RUNTIME_CONFIG.discord.humanDelay,
|
|
1166
|
+
),
|
|
1167
|
+
typingMode: normalizeDiscordTypingMode(
|
|
1168
|
+
rawDiscord.typingMode,
|
|
1169
|
+
DEFAULT_RUNTIME_CONFIG.discord.typingMode,
|
|
1170
|
+
),
|
|
1171
|
+
presence: normalizeDiscordPresenceConfig(
|
|
1172
|
+
rawDiscord.presence,
|
|
1173
|
+
DEFAULT_RUNTIME_CONFIG.discord.presence,
|
|
1174
|
+
),
|
|
1175
|
+
lifecycleReactions: normalizeDiscordLifecycleReactionsConfig(
|
|
1176
|
+
rawDiscord.lifecycleReactions,
|
|
1177
|
+
DEFAULT_RUNTIME_CONFIG.discord.lifecycleReactions,
|
|
1178
|
+
),
|
|
1179
|
+
ackReaction: normalizeString(
|
|
1180
|
+
rawDiscord.ackReaction,
|
|
1181
|
+
DEFAULT_RUNTIME_CONFIG.discord.ackReaction,
|
|
1182
|
+
{ allowEmpty: false },
|
|
1183
|
+
),
|
|
1184
|
+
ackReactionScope: normalizeDiscordAckReactionScope(
|
|
1185
|
+
rawDiscord.ackReactionScope,
|
|
1186
|
+
DEFAULT_RUNTIME_CONFIG.discord.ackReactionScope,
|
|
1187
|
+
),
|
|
1188
|
+
removeAckAfterReply: normalizeBoolean(
|
|
1189
|
+
rawDiscord.removeAckAfterReply,
|
|
1190
|
+
DEFAULT_RUNTIME_CONFIG.discord.removeAckAfterReply,
|
|
1191
|
+
),
|
|
1192
|
+
debounceMs: normalizeInteger(
|
|
1193
|
+
rawDiscord.debounceMs,
|
|
1194
|
+
DEFAULT_RUNTIME_CONFIG.discord.debounceMs,
|
|
1195
|
+
{ min: 0, max: 120_000 },
|
|
1196
|
+
),
|
|
1197
|
+
rateLimitPerUser: normalizeInteger(
|
|
1198
|
+
rawDiscord.rateLimitPerUser,
|
|
1199
|
+
DEFAULT_RUNTIME_CONFIG.discord.rateLimitPerUser,
|
|
1200
|
+
{ min: 0, max: 300 },
|
|
1201
|
+
),
|
|
1202
|
+
rateLimitExemptRoles: normalizeStringArray(
|
|
1203
|
+
rawDiscord.rateLimitExemptRoles,
|
|
1204
|
+
DEFAULT_RUNTIME_CONFIG.discord.rateLimitExemptRoles,
|
|
1205
|
+
),
|
|
1206
|
+
suppressPatterns: normalizeStringArray(
|
|
1207
|
+
rawDiscord.suppressPatterns,
|
|
1208
|
+
DEFAULT_RUNTIME_CONFIG.discord.suppressPatterns,
|
|
1209
|
+
),
|
|
1210
|
+
maxConcurrentPerChannel: normalizeInteger(
|
|
1211
|
+
rawDiscord.maxConcurrentPerChannel,
|
|
1212
|
+
DEFAULT_RUNTIME_CONFIG.discord.maxConcurrentPerChannel,
|
|
1213
|
+
{ min: 1, max: 16 },
|
|
1214
|
+
),
|
|
1215
|
+
guilds: normalizeDiscordGuildMap(
|
|
1216
|
+
rawDiscord.guilds,
|
|
1217
|
+
DEFAULT_RUNTIME_CONFIG.discord.guilds,
|
|
1218
|
+
),
|
|
422
1219
|
},
|
|
423
1220
|
hybridai: {
|
|
424
1221
|
baseUrl: hybridBaseUrl,
|
|
425
|
-
defaultModel: normalizeString(
|
|
1222
|
+
defaultModel: normalizeString(
|
|
1223
|
+
rawHybridAi.defaultModel,
|
|
1224
|
+
DEFAULT_RUNTIME_CONFIG.hybridai.defaultModel,
|
|
1225
|
+
{ allowEmpty: false },
|
|
1226
|
+
),
|
|
426
1227
|
defaultChatbotId: hybridDefaultChatbotId,
|
|
427
|
-
enableRag: normalizeBoolean(
|
|
1228
|
+
enableRag: normalizeBoolean(
|
|
1229
|
+
rawHybridAi.enableRag,
|
|
1230
|
+
DEFAULT_RUNTIME_CONFIG.hybridai.enableRag,
|
|
1231
|
+
),
|
|
428
1232
|
models: modelList,
|
|
429
1233
|
},
|
|
430
1234
|
container: {
|
|
431
|
-
image: normalizeString(
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
1235
|
+
image: normalizeString(
|
|
1236
|
+
rawContainer.image,
|
|
1237
|
+
DEFAULT_RUNTIME_CONFIG.container.image,
|
|
1238
|
+
{ allowEmpty: false },
|
|
1239
|
+
),
|
|
1240
|
+
memory: normalizeString(
|
|
1241
|
+
rawContainer.memory,
|
|
1242
|
+
DEFAULT_RUNTIME_CONFIG.container.memory,
|
|
1243
|
+
{ allowEmpty: false },
|
|
1244
|
+
),
|
|
1245
|
+
cpus: normalizeString(
|
|
1246
|
+
rawContainer.cpus,
|
|
1247
|
+
DEFAULT_RUNTIME_CONFIG.container.cpus,
|
|
1248
|
+
{ allowEmpty: false },
|
|
1249
|
+
),
|
|
1250
|
+
timeoutMs: normalizeInteger(
|
|
1251
|
+
rawContainer.timeoutMs,
|
|
1252
|
+
DEFAULT_RUNTIME_CONFIG.container.timeoutMs,
|
|
1253
|
+
{ min: 1_000 },
|
|
1254
|
+
),
|
|
1255
|
+
additionalMounts: normalizeString(
|
|
1256
|
+
rawContainer.additionalMounts,
|
|
1257
|
+
DEFAULT_RUNTIME_CONFIG.container.additionalMounts,
|
|
1258
|
+
{ allowEmpty: true },
|
|
1259
|
+
),
|
|
1260
|
+
maxOutputBytes: normalizeInteger(
|
|
1261
|
+
rawContainer.maxOutputBytes,
|
|
1262
|
+
DEFAULT_RUNTIME_CONFIG.container.maxOutputBytes,
|
|
1263
|
+
{ min: 1_024 },
|
|
1264
|
+
),
|
|
1265
|
+
maxConcurrent: normalizeInteger(
|
|
1266
|
+
rawContainer.maxConcurrent,
|
|
1267
|
+
DEFAULT_RUNTIME_CONFIG.container.maxConcurrent,
|
|
1268
|
+
{ min: 1 },
|
|
1269
|
+
),
|
|
438
1270
|
},
|
|
439
1271
|
heartbeat: {
|
|
440
|
-
enabled: normalizeBoolean(
|
|
441
|
-
|
|
442
|
-
|
|
1272
|
+
enabled: normalizeBoolean(
|
|
1273
|
+
rawHeartbeat.enabled,
|
|
1274
|
+
DEFAULT_RUNTIME_CONFIG.heartbeat.enabled,
|
|
1275
|
+
),
|
|
1276
|
+
intervalMs: normalizeInteger(
|
|
1277
|
+
rawHeartbeat.intervalMs,
|
|
1278
|
+
DEFAULT_RUNTIME_CONFIG.heartbeat.intervalMs,
|
|
1279
|
+
{ min: 10_000 },
|
|
1280
|
+
),
|
|
1281
|
+
channel: normalizeString(
|
|
1282
|
+
rawHeartbeat.channel,
|
|
1283
|
+
DEFAULT_RUNTIME_CONFIG.heartbeat.channel,
|
|
1284
|
+
{ allowEmpty: true },
|
|
1285
|
+
),
|
|
1286
|
+
},
|
|
1287
|
+
memory: {
|
|
1288
|
+
decayRate: normalizeNumber(
|
|
1289
|
+
rawMemory.decayRate,
|
|
1290
|
+
DEFAULT_RUNTIME_CONFIG.memory.decayRate,
|
|
1291
|
+
{ min: 0, max: 0.95 },
|
|
1292
|
+
),
|
|
1293
|
+
consolidationIntervalHours: normalizeInteger(
|
|
1294
|
+
rawMemory.consolidationIntervalHours,
|
|
1295
|
+
DEFAULT_RUNTIME_CONFIG.memory.consolidationIntervalHours,
|
|
1296
|
+
{ min: 0, max: 24 * 30 },
|
|
1297
|
+
),
|
|
443
1298
|
},
|
|
444
1299
|
ops: {
|
|
445
|
-
healthHost: normalizeString(rawOps.healthHost, defaultOps.healthHost, {
|
|
1300
|
+
healthHost: normalizeString(rawOps.healthHost, defaultOps.healthHost, {
|
|
1301
|
+
allowEmpty: false,
|
|
1302
|
+
}),
|
|
446
1303
|
healthPort,
|
|
447
1304
|
webApiToken,
|
|
448
|
-
gatewayBaseUrl: normalizeBaseUrl(
|
|
449
|
-
|
|
450
|
-
|
|
1305
|
+
gatewayBaseUrl: normalizeBaseUrl(
|
|
1306
|
+
rawOps.gatewayBaseUrl,
|
|
1307
|
+
`http://127.0.0.1:${healthPort}`,
|
|
1308
|
+
),
|
|
1309
|
+
gatewayApiToken: normalizeString(rawOps.gatewayApiToken, webApiToken, {
|
|
1310
|
+
allowEmpty: true,
|
|
1311
|
+
}),
|
|
1312
|
+
dbPath: normalizeString(rawOps.dbPath, defaultOps.dbPath, {
|
|
1313
|
+
allowEmpty: false,
|
|
1314
|
+
}),
|
|
451
1315
|
logLevel: normalizeLogLevel(rawOps.logLevel, defaultOps.logLevel),
|
|
452
1316
|
},
|
|
453
1317
|
observability: {
|
|
454
|
-
enabled: normalizeBoolean(
|
|
1318
|
+
enabled: normalizeBoolean(
|
|
1319
|
+
rawObservability.enabled,
|
|
1320
|
+
DEFAULT_RUNTIME_CONFIG.observability.enabled,
|
|
1321
|
+
),
|
|
455
1322
|
baseUrl: normalizeBaseUrl(rawObservability.baseUrl, hybridBaseUrl),
|
|
456
|
-
ingestPath: normalizeApiPath(
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
1323
|
+
ingestPath: normalizeApiPath(
|
|
1324
|
+
rawObservability.ingestPath,
|
|
1325
|
+
DEFAULT_RUNTIME_CONFIG.observability.ingestPath,
|
|
1326
|
+
),
|
|
1327
|
+
statusPath: normalizeApiPath(
|
|
1328
|
+
rawObservability.statusPath,
|
|
1329
|
+
DEFAULT_RUNTIME_CONFIG.observability.statusPath,
|
|
1330
|
+
),
|
|
1331
|
+
botId: normalizeString(rawObservability.botId, hybridDefaultChatbotId, {
|
|
1332
|
+
allowEmpty: true,
|
|
1333
|
+
}),
|
|
1334
|
+
agentId: normalizeString(
|
|
1335
|
+
rawObservability.agentId,
|
|
1336
|
+
DEFAULT_RUNTIME_CONFIG.observability.agentId,
|
|
1337
|
+
{ allowEmpty: false },
|
|
1338
|
+
),
|
|
1339
|
+
label: normalizeString(
|
|
1340
|
+
rawObservability.label,
|
|
1341
|
+
DEFAULT_RUNTIME_CONFIG.observability.label,
|
|
1342
|
+
{ allowEmpty: true },
|
|
1343
|
+
),
|
|
1344
|
+
environment: normalizeString(
|
|
1345
|
+
rawObservability.environment,
|
|
1346
|
+
DEFAULT_RUNTIME_CONFIG.observability.environment,
|
|
1347
|
+
{ allowEmpty: false },
|
|
1348
|
+
),
|
|
1349
|
+
flushIntervalMs: normalizeInteger(
|
|
1350
|
+
rawObservability.flushIntervalMs,
|
|
1351
|
+
DEFAULT_RUNTIME_CONFIG.observability.flushIntervalMs,
|
|
1352
|
+
{ min: 1_000, max: 3_600_000 },
|
|
1353
|
+
),
|
|
1354
|
+
batchMaxEvents: normalizeInteger(
|
|
1355
|
+
rawObservability.batchMaxEvents,
|
|
1356
|
+
DEFAULT_RUNTIME_CONFIG.observability.batchMaxEvents,
|
|
1357
|
+
{ min: 1, max: 1_000 },
|
|
1358
|
+
),
|
|
464
1359
|
},
|
|
465
1360
|
sessionCompaction: {
|
|
466
|
-
enabled: normalizeBoolean(
|
|
1361
|
+
enabled: normalizeBoolean(
|
|
1362
|
+
rawSessionCompaction.enabled,
|
|
1363
|
+
DEFAULT_RUNTIME_CONFIG.sessionCompaction.enabled,
|
|
1364
|
+
),
|
|
1365
|
+
tokenBudget,
|
|
1366
|
+
budgetRatio,
|
|
467
1367
|
threshold,
|
|
468
1368
|
keepRecent,
|
|
469
1369
|
summaryMaxChars: normalizeInteger(
|
|
@@ -472,49 +1372,125 @@ function normalizeRuntimeConfig(patch?: DeepPartial<RuntimeConfig>): RuntimeConf
|
|
|
472
1372
|
{ min: 1_000 },
|
|
473
1373
|
),
|
|
474
1374
|
preCompactionMemoryFlush: {
|
|
475
|
-
enabled: normalizeBoolean(
|
|
1375
|
+
enabled: normalizeBoolean(
|
|
1376
|
+
rawPreFlush.enabled,
|
|
1377
|
+
DEFAULT_RUNTIME_CONFIG.sessionCompaction.preCompactionMemoryFlush
|
|
1378
|
+
.enabled,
|
|
1379
|
+
),
|
|
476
1380
|
maxMessages: normalizeInteger(
|
|
477
1381
|
rawPreFlush.maxMessages,
|
|
478
|
-
DEFAULT_RUNTIME_CONFIG.sessionCompaction.preCompactionMemoryFlush
|
|
1382
|
+
DEFAULT_RUNTIME_CONFIG.sessionCompaction.preCompactionMemoryFlush
|
|
1383
|
+
.maxMessages,
|
|
479
1384
|
{ min: 8 },
|
|
480
1385
|
),
|
|
481
1386
|
maxChars: normalizeInteger(
|
|
482
1387
|
rawPreFlush.maxChars,
|
|
483
|
-
DEFAULT_RUNTIME_CONFIG.sessionCompaction.preCompactionMemoryFlush
|
|
1388
|
+
DEFAULT_RUNTIME_CONFIG.sessionCompaction.preCompactionMemoryFlush
|
|
1389
|
+
.maxChars,
|
|
484
1390
|
{ min: 4_000 },
|
|
485
1391
|
),
|
|
486
1392
|
},
|
|
487
1393
|
},
|
|
488
1394
|
promptHooks: {
|
|
489
|
-
bootstrapEnabled: normalizeBoolean(
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
1395
|
+
bootstrapEnabled: normalizeBoolean(
|
|
1396
|
+
rawPromptHooks.bootstrapEnabled,
|
|
1397
|
+
DEFAULT_RUNTIME_CONFIG.promptHooks.bootstrapEnabled,
|
|
1398
|
+
),
|
|
1399
|
+
memoryEnabled: normalizeBoolean(
|
|
1400
|
+
rawPromptHooks.memoryEnabled,
|
|
1401
|
+
DEFAULT_RUNTIME_CONFIG.promptHooks.memoryEnabled,
|
|
1402
|
+
),
|
|
1403
|
+
safetyEnabled: normalizeBoolean(
|
|
1404
|
+
rawPromptHooks.safetyEnabled,
|
|
1405
|
+
DEFAULT_RUNTIME_CONFIG.promptHooks.safetyEnabled,
|
|
1406
|
+
),
|
|
1407
|
+
proactivityEnabled: normalizeBoolean(
|
|
1408
|
+
rawPromptHooks.proactivityEnabled,
|
|
1409
|
+
DEFAULT_RUNTIME_CONFIG.promptHooks.proactivityEnabled,
|
|
1410
|
+
),
|
|
493
1411
|
},
|
|
494
1412
|
proactive: {
|
|
495
1413
|
activeHours: {
|
|
496
|
-
enabled: normalizeBoolean(
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
1414
|
+
enabled: normalizeBoolean(
|
|
1415
|
+
rawActiveHours.enabled,
|
|
1416
|
+
DEFAULT_RUNTIME_CONFIG.proactive.activeHours.enabled,
|
|
1417
|
+
),
|
|
1418
|
+
timezone: normalizeString(
|
|
1419
|
+
rawActiveHours.timezone,
|
|
1420
|
+
DEFAULT_RUNTIME_CONFIG.proactive.activeHours.timezone,
|
|
1421
|
+
{ allowEmpty: true },
|
|
1422
|
+
),
|
|
1423
|
+
startHour: normalizeInteger(
|
|
1424
|
+
rawActiveHours.startHour,
|
|
1425
|
+
DEFAULT_RUNTIME_CONFIG.proactive.activeHours.startHour,
|
|
1426
|
+
{ min: 0, max: 23 },
|
|
1427
|
+
),
|
|
1428
|
+
endHour: normalizeInteger(
|
|
1429
|
+
rawActiveHours.endHour,
|
|
1430
|
+
DEFAULT_RUNTIME_CONFIG.proactive.activeHours.endHour,
|
|
1431
|
+
{ min: 0, max: 23 },
|
|
1432
|
+
),
|
|
1433
|
+
queueOutsideHours: normalizeBoolean(
|
|
1434
|
+
rawActiveHours.queueOutsideHours,
|
|
1435
|
+
DEFAULT_RUNTIME_CONFIG.proactive.activeHours.queueOutsideHours,
|
|
1436
|
+
),
|
|
501
1437
|
},
|
|
502
1438
|
delegation: {
|
|
503
|
-
enabled: normalizeBoolean(
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
1439
|
+
enabled: normalizeBoolean(
|
|
1440
|
+
rawDelegation.enabled,
|
|
1441
|
+
DEFAULT_RUNTIME_CONFIG.proactive.delegation.enabled,
|
|
1442
|
+
),
|
|
1443
|
+
maxConcurrent: normalizeInteger(
|
|
1444
|
+
rawDelegation.maxConcurrent,
|
|
1445
|
+
DEFAULT_RUNTIME_CONFIG.proactive.delegation.maxConcurrent,
|
|
1446
|
+
{ min: 1, max: 8 },
|
|
1447
|
+
),
|
|
1448
|
+
maxDepth: normalizeInteger(
|
|
1449
|
+
rawDelegation.maxDepth,
|
|
1450
|
+
DEFAULT_RUNTIME_CONFIG.proactive.delegation.maxDepth,
|
|
1451
|
+
{ min: 1, max: 4 },
|
|
1452
|
+
),
|
|
1453
|
+
maxPerTurn: normalizeInteger(
|
|
1454
|
+
rawDelegation.maxPerTurn,
|
|
1455
|
+
DEFAULT_RUNTIME_CONFIG.proactive.delegation.maxPerTurn,
|
|
1456
|
+
{ min: 1, max: 8 },
|
|
1457
|
+
),
|
|
507
1458
|
},
|
|
508
1459
|
autoRetry: {
|
|
509
|
-
enabled: normalizeBoolean(
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
1460
|
+
enabled: normalizeBoolean(
|
|
1461
|
+
rawAutoRetry.enabled,
|
|
1462
|
+
DEFAULT_RUNTIME_CONFIG.proactive.autoRetry.enabled,
|
|
1463
|
+
),
|
|
1464
|
+
maxAttempts: normalizeInteger(
|
|
1465
|
+
rawAutoRetry.maxAttempts,
|
|
1466
|
+
DEFAULT_RUNTIME_CONFIG.proactive.autoRetry.maxAttempts,
|
|
1467
|
+
{ min: 1, max: 8 },
|
|
1468
|
+
),
|
|
1469
|
+
baseDelayMs: normalizeInteger(
|
|
1470
|
+
rawAutoRetry.baseDelayMs,
|
|
1471
|
+
DEFAULT_RUNTIME_CONFIG.proactive.autoRetry.baseDelayMs,
|
|
1472
|
+
{ min: 100, max: 120_000 },
|
|
1473
|
+
),
|
|
1474
|
+
maxDelayMs: normalizeInteger(
|
|
1475
|
+
rawAutoRetry.maxDelayMs,
|
|
1476
|
+
DEFAULT_RUNTIME_CONFIG.proactive.autoRetry.maxDelayMs,
|
|
1477
|
+
{ min: 100, max: 600_000 },
|
|
1478
|
+
),
|
|
513
1479
|
},
|
|
514
1480
|
ralph: {
|
|
515
|
-
maxIterations: normalizeInteger(
|
|
1481
|
+
maxIterations: normalizeInteger(
|
|
1482
|
+
rawRalph.maxIterations,
|
|
1483
|
+
DEFAULT_RUNTIME_CONFIG.proactive.ralph.maxIterations,
|
|
1484
|
+
{ min: -1, max: 64 },
|
|
1485
|
+
),
|
|
516
1486
|
},
|
|
517
1487
|
},
|
|
1488
|
+
scheduler: {
|
|
1489
|
+
jobs: normalizeSchedulerJobList(
|
|
1490
|
+
rawScheduler.jobs,
|
|
1491
|
+
DEFAULT_RUNTIME_CONFIG.scheduler.jobs,
|
|
1492
|
+
),
|
|
1493
|
+
},
|
|
518
1494
|
};
|
|
519
1495
|
}
|
|
520
1496
|
|
|
@@ -544,7 +1520,9 @@ function applyConfig(next: RuntimeConfig): void {
|
|
|
544
1520
|
try {
|
|
545
1521
|
listener(cloneConfig(currentConfig), cloneConfig(prev));
|
|
546
1522
|
} catch (err) {
|
|
547
|
-
console.warn(
|
|
1523
|
+
console.warn(
|
|
1524
|
+
`[runtime-config] listener failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
1525
|
+
);
|
|
548
1526
|
}
|
|
549
1527
|
}
|
|
550
1528
|
}
|
|
@@ -559,7 +1537,9 @@ function reloadFromDisk(trigger: string): void {
|
|
|
559
1537
|
const next = loadRuntimeConfigFromSources();
|
|
560
1538
|
applyConfig(next);
|
|
561
1539
|
} catch (err) {
|
|
562
|
-
console.warn(
|
|
1540
|
+
console.warn(
|
|
1541
|
+
`[runtime-config] reload failed (${trigger}): ${err instanceof Error ? err.message : String(err)}`,
|
|
1542
|
+
);
|
|
563
1543
|
}
|
|
564
1544
|
}
|
|
565
1545
|
|
|
@@ -574,13 +1554,15 @@ function scheduleReload(trigger: string): void {
|
|
|
574
1554
|
function scheduleWatcherRestart(reason: string): void {
|
|
575
1555
|
if (watcherRestartTimer) return;
|
|
576
1556
|
if (watcherRetryAttempt >= WATCHER_RETRY_MAX_ATTEMPTS) {
|
|
577
|
-
console.warn(
|
|
1557
|
+
console.warn(
|
|
1558
|
+
`[runtime-config] watcher disabled after ${WATCHER_RETRY_MAX_ATTEMPTS} retries (${reason})`,
|
|
1559
|
+
);
|
|
578
1560
|
return;
|
|
579
1561
|
}
|
|
580
1562
|
|
|
581
1563
|
watcherRetryAttempt += 1;
|
|
582
1564
|
const delay = Math.min(
|
|
583
|
-
WATCHER_RETRY_BASE_DELAY_MS *
|
|
1565
|
+
WATCHER_RETRY_BASE_DELAY_MS * 2 ** (watcherRetryAttempt - 1),
|
|
584
1566
|
WATCHER_RETRY_MAX_DELAY_MS,
|
|
585
1567
|
);
|
|
586
1568
|
console.warn(
|
|
@@ -596,14 +1578,18 @@ function startWatcher(): void {
|
|
|
596
1578
|
if (configWatcher) return;
|
|
597
1579
|
|
|
598
1580
|
try {
|
|
599
|
-
configWatcher = fs.watch(
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
1581
|
+
configWatcher = fs.watch(
|
|
1582
|
+
path.dirname(CONFIG_PATH),
|
|
1583
|
+
{ persistent: false },
|
|
1584
|
+
(_event, filename) => {
|
|
1585
|
+
if (!filename) {
|
|
1586
|
+
scheduleReload('unknown');
|
|
1587
|
+
return;
|
|
1588
|
+
}
|
|
1589
|
+
if (filename.toString() !== path.basename(CONFIG_PATH)) return;
|
|
1590
|
+
scheduleReload(`watch:${filename.toString()}`);
|
|
1591
|
+
},
|
|
1592
|
+
);
|
|
607
1593
|
watcherRetryAttempt = 0;
|
|
608
1594
|
if (watcherRestartTimer) {
|
|
609
1595
|
clearTimeout(watcherRestartTimer);
|
|
@@ -639,21 +1625,28 @@ function migrateConfigSchemaOnStartup(): void {
|
|
|
639
1625
|
raw = fs.readFileSync(CONFIG_PATH, 'utf-8');
|
|
640
1626
|
parsed = JSON.parse(raw) as unknown;
|
|
641
1627
|
} catch (err) {
|
|
642
|
-
console.warn(
|
|
1628
|
+
console.warn(
|
|
1629
|
+
`[runtime-config] schema migration skipped (invalid JSON): ${err instanceof Error ? err.message : String(err)}`,
|
|
1630
|
+
);
|
|
643
1631
|
return;
|
|
644
1632
|
}
|
|
645
1633
|
|
|
646
1634
|
if (!isRecord(parsed)) {
|
|
647
|
-
console.warn(
|
|
1635
|
+
console.warn(
|
|
1636
|
+
'[runtime-config] schema migration skipped: config.json is not an object',
|
|
1637
|
+
);
|
|
648
1638
|
return;
|
|
649
1639
|
}
|
|
650
1640
|
|
|
651
|
-
const previousVersion =
|
|
1641
|
+
const previousVersion =
|
|
1642
|
+
typeof parsed.version === 'number' ? parsed.version : null;
|
|
652
1643
|
let migrated: RuntimeConfig;
|
|
653
1644
|
try {
|
|
654
1645
|
migrated = normalizeRuntimeConfig(parseConfigPatch(parsed));
|
|
655
1646
|
} catch (err) {
|
|
656
|
-
console.warn(
|
|
1647
|
+
console.warn(
|
|
1648
|
+
`[runtime-config] schema migration skipped: ${err instanceof Error ? err.message : String(err)}`,
|
|
1649
|
+
);
|
|
657
1650
|
return;
|
|
658
1651
|
}
|
|
659
1652
|
|
|
@@ -664,12 +1657,18 @@ function migrateConfigSchemaOnStartup(): void {
|
|
|
664
1657
|
writeConfigFile(migrated);
|
|
665
1658
|
const from = previousVersion == null ? 'unknown' : String(previousVersion);
|
|
666
1659
|
if (previousVersion !== CONFIG_VERSION) {
|
|
667
|
-
console.info(
|
|
1660
|
+
console.info(
|
|
1661
|
+
`[runtime-config] migrated config schema from v${from} to v${CONFIG_VERSION}`,
|
|
1662
|
+
);
|
|
668
1663
|
} else {
|
|
669
|
-
console.info(
|
|
1664
|
+
console.info(
|
|
1665
|
+
`[runtime-config] normalized config schema v${CONFIG_VERSION} (filled defaults/canonicalized values)`,
|
|
1666
|
+
);
|
|
670
1667
|
}
|
|
671
1668
|
} catch (err) {
|
|
672
|
-
console.warn(
|
|
1669
|
+
console.warn(
|
|
1670
|
+
`[runtime-config] schema migration failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
1671
|
+
);
|
|
673
1672
|
}
|
|
674
1673
|
}
|
|
675
1674
|
|
|
@@ -697,7 +1696,9 @@ export function getRuntimeConfig(): RuntimeConfig {
|
|
|
697
1696
|
return cloneConfig(currentConfig);
|
|
698
1697
|
}
|
|
699
1698
|
|
|
700
|
-
export function onRuntimeConfigChange(
|
|
1699
|
+
export function onRuntimeConfigChange(
|
|
1700
|
+
listener: RuntimeConfigChangeListener,
|
|
1701
|
+
): () => void {
|
|
701
1702
|
listeners.add(listener);
|
|
702
1703
|
return () => listeners.delete(listener);
|
|
703
1704
|
}
|
|
@@ -709,17 +1710,21 @@ export function saveRuntimeConfig(next: RuntimeConfig): RuntimeConfig {
|
|
|
709
1710
|
return cloneConfig(normalized);
|
|
710
1711
|
}
|
|
711
1712
|
|
|
712
|
-
export function updateRuntimeConfig(
|
|
1713
|
+
export function updateRuntimeConfig(
|
|
1714
|
+
mutator: (draft: RuntimeConfig) => void,
|
|
1715
|
+
): RuntimeConfig {
|
|
713
1716
|
const draft = cloneConfig(currentConfig);
|
|
714
1717
|
mutator(draft);
|
|
715
1718
|
return saveRuntimeConfig(draft);
|
|
716
1719
|
}
|
|
717
1720
|
|
|
718
|
-
export function isSecurityTrustAccepted(
|
|
1721
|
+
export function isSecurityTrustAccepted(
|
|
1722
|
+
config: RuntimeConfig = currentConfig,
|
|
1723
|
+
): boolean {
|
|
719
1724
|
return Boolean(
|
|
720
|
-
config.security.trustModelAccepted
|
|
721
|
-
|
|
722
|
-
|
|
1725
|
+
config.security.trustModelAccepted &&
|
|
1726
|
+
config.security.trustModelAcceptedAt &&
|
|
1727
|
+
config.security.trustModelVersion === SECURITY_POLICY_VERSION,
|
|
723
1728
|
);
|
|
724
1729
|
}
|
|
725
1730
|
|
|
@@ -728,9 +1733,19 @@ export function acceptSecurityTrustModel(params?: {
|
|
|
728
1733
|
acceptedBy?: string | null;
|
|
729
1734
|
policyVersion?: string;
|
|
730
1735
|
}): RuntimeConfig {
|
|
731
|
-
const acceptedAt = normalizeString(
|
|
732
|
-
|
|
733
|
-
|
|
1736
|
+
const acceptedAt = normalizeString(
|
|
1737
|
+
params?.acceptedAt,
|
|
1738
|
+
new Date().toISOString(),
|
|
1739
|
+
{ allowEmpty: false },
|
|
1740
|
+
);
|
|
1741
|
+
const acceptedBy = normalizeString(params?.acceptedBy ?? '', '', {
|
|
1742
|
+
allowEmpty: true,
|
|
1743
|
+
});
|
|
1744
|
+
const policyVersion = normalizeString(
|
|
1745
|
+
params?.policyVersion,
|
|
1746
|
+
SECURITY_POLICY_VERSION,
|
|
1747
|
+
{ allowEmpty: false },
|
|
1748
|
+
);
|
|
734
1749
|
|
|
735
1750
|
return updateRuntimeConfig((draft) => {
|
|
736
1751
|
draft.security.trustModelAccepted = true;
|