@hybridaione/hybridclaw 0.2.3 → 0.2.7
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 +95 -0
- package/CONTRIBUTING.md +33 -0
- package/README.md +57 -19
- package/SECURITY.md +17 -0
- package/biome.json +35 -0
- package/config.example.json +72 -7
- package/container/package-lock.json +2 -2
- package/container/package.json +1 -1
- package/container/src/approval-policy.ts +1405 -0
- package/container/src/browser-tools.ts +672 -136
- package/container/src/extensions.ts +36 -12
- package/container/src/hybridai-client.ts +65 -16
- package/container/src/index.ts +463 -110
- package/container/src/ipc.ts +5 -3
- package/container/src/token-usage.ts +107 -18
- package/container/src/tools.ts +600 -225
- package/container/src/types.ts +49 -2
- package/container/src/web-fetch.ts +89 -32
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +15 -4
- 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 +24 -4
- 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 +31 -0
- package/dist/channels/discord/inbound.d.ts.map +1 -1
- package/dist/channels/discord/inbound.js +156 -6
- 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 +7 -4
- package/dist/channels/discord/runtime.d.ts.map +1 -1
- package/dist/channels/discord/runtime.js +755 -150
- 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 +19 -12
- 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 +77 -23
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +24 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +113 -18
- package/dist/config.js.map +1 -1
- package/dist/container-runner.d.ts.map +1 -1
- package/dist/container-runner.js +59 -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 +624 -87
- 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 +303 -26
- 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 +62 -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/hybridai-models.d.ts +8 -0
- package/dist/hybridai-models.d.ts.map +1 -0
- package/dist/hybridai-models.js +94 -0
- package/dist/hybridai-models.js.map +1 -0
- 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 +11 -7
- package/dist/prompt-hooks.js.map +1 -1
- package/dist/runtime-config.d.ts +104 -1
- package/dist/runtime-config.d.ts.map +1 -1
- package/dist/runtime-config.js +503 -24
- 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 +43 -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 +150 -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 +72 -13
- package/package.json +8 -2
- package/src/agent.ts +31 -4
- 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 +65 -14
- package/src/channels/discord/human-delay.ts +48 -0
- package/src/channels/discord/inbound.ts +190 -8
- 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 +1161 -205
- package/src/channels/discord/stream.ts +85 -31
- 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 +174 -57
- package/src/config.ts +208 -36
- package/src/container-runner.ts +134 -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 +996 -220
- package/src/gateway-types.ts +33 -0
- package/src/gateway.ts +487 -52
- package/src/health.ts +66 -26
- package/src/heartbeat.ts +99 -22
- package/src/hybridai-bots.ts +14 -5
- package/src/hybridai-models.ts +158 -0
- 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 +42 -17
- package/src/runtime-config.ts +1112 -122
- package/src/scheduled-task-runner.ts +77 -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 +199 -2
- package/src/update.ts +79 -23
- package/src/workspace.ts +63 -11
- package/tests/approval-policy.test.ts +335 -0
- package/tests/discord.basic.test.ts +213 -4
- package/tests/discord.human-presence.test.ts +85 -0
- package/tests/gateway-service.media-routing.test.ts +8 -2
- package/tests/hybridai-client.test.ts +112 -0
- package/tests/hybridai-models.test.ts +46 -0
- package/tests/memory-service.test.ts +1114 -0
- package/tests/token-efficiency.basic.test.ts +8 -2
- package/tests/token-usage.cache.test.ts +128 -0
- 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/config.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
5
6
|
|
|
6
7
|
import { loadEnvFile } from './env.js';
|
|
7
8
|
import {
|
|
@@ -27,13 +28,9 @@ function required(name: string): string {
|
|
|
27
28
|
return val;
|
|
28
29
|
}
|
|
29
30
|
|
|
30
|
-
function
|
|
31
|
-
const envVersion = process.env.npm_package_version;
|
|
32
|
-
if (envVersion) return envVersion;
|
|
33
|
-
|
|
34
|
-
const packagePath = path.join(process.cwd(), 'package.json');
|
|
31
|
+
function readVersionFromPackageJson(packageJsonPath: string): string | null {
|
|
35
32
|
try {
|
|
36
|
-
const raw = fs.readFileSync(
|
|
33
|
+
const raw = fs.readFileSync(packageJsonPath, 'utf-8');
|
|
37
34
|
const parsed = JSON.parse(raw) as { version?: unknown };
|
|
38
35
|
if (typeof parsed.version === 'string' && parsed.version.trim()) {
|
|
39
36
|
return parsed.version.trim();
|
|
@@ -41,6 +38,31 @@ function resolveAppVersion(): string {
|
|
|
41
38
|
} catch {
|
|
42
39
|
// fall through
|
|
43
40
|
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function resolveAppVersion(): string {
|
|
45
|
+
const envVersion = process.env.npm_package_version;
|
|
46
|
+
if (envVersion?.trim()) return envVersion.trim();
|
|
47
|
+
|
|
48
|
+
const modulePath = fileURLToPath(import.meta.url);
|
|
49
|
+
const moduleVersion = readVersionFromPackageJson(
|
|
50
|
+
path.join(path.dirname(modulePath), '..', 'package.json'),
|
|
51
|
+
);
|
|
52
|
+
if (moduleVersion) return moduleVersion;
|
|
53
|
+
|
|
54
|
+
const entryPath = process.argv[1];
|
|
55
|
+
if (entryPath) {
|
|
56
|
+
const entryVersion = readVersionFromPackageJson(
|
|
57
|
+
path.join(path.dirname(path.resolve(entryPath)), '..', 'package.json'),
|
|
58
|
+
);
|
|
59
|
+
if (entryVersion) return entryVersion;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const cwdVersion = readVersionFromPackageJson(
|
|
63
|
+
path.join(process.cwd(), 'package.json'),
|
|
64
|
+
);
|
|
65
|
+
if (cwdVersion) return cwdVersion;
|
|
44
66
|
|
|
45
67
|
return '0.0.0';
|
|
46
68
|
}
|
|
@@ -61,14 +83,63 @@ export let DISCORD_GUILD_MEMBERS_INTENT = false;
|
|
|
61
83
|
export let DISCORD_PRESENCE_INTENT = false;
|
|
62
84
|
export let DISCORD_RESPOND_TO_ALL_MESSAGES = false;
|
|
63
85
|
export let DISCORD_COMMANDS_ONLY = false;
|
|
86
|
+
export let DISCORD_COMMAND_MODE: RuntimeConfig['discord']['commandMode'] =
|
|
87
|
+
'public';
|
|
88
|
+
export let DISCORD_COMMAND_ALLOWED_USER_IDS: string[] = [];
|
|
64
89
|
export let DISCORD_COMMAND_USER_ID = '';
|
|
65
|
-
export let DISCORD_GROUP_POLICY: RuntimeConfig['discord']['groupPolicy'] =
|
|
90
|
+
export let DISCORD_GROUP_POLICY: RuntimeConfig['discord']['groupPolicy'] =
|
|
91
|
+
'open';
|
|
66
92
|
export let DISCORD_FREE_RESPONSE_CHANNELS: string[] = [];
|
|
93
|
+
export let DISCORD_TEXT_CHUNK_LIMIT = 2_000;
|
|
94
|
+
export let DISCORD_MAX_LINES_PER_MESSAGE = 17;
|
|
95
|
+
export let DISCORD_HUMAN_DELAY: RuntimeConfig['discord']['humanDelay'] = {
|
|
96
|
+
mode: 'natural',
|
|
97
|
+
minMs: 800,
|
|
98
|
+
maxMs: 2_500,
|
|
99
|
+
};
|
|
100
|
+
export let DISCORD_TYPING_MODE: RuntimeConfig['discord']['typingMode'] =
|
|
101
|
+
'thinking';
|
|
102
|
+
export let DISCORD_SELF_PRESENCE: RuntimeConfig['discord']['presence'] = {
|
|
103
|
+
enabled: true,
|
|
104
|
+
intervalMs: 30_000,
|
|
105
|
+
healthyText: 'Watching the channels',
|
|
106
|
+
degradedText: 'Thinking slowly...',
|
|
107
|
+
exhaustedText: 'Taking a break',
|
|
108
|
+
activityType: 'watching',
|
|
109
|
+
};
|
|
110
|
+
export let DISCORD_LIFECYCLE_REACTIONS: RuntimeConfig['discord']['lifecycleReactions'] =
|
|
111
|
+
{
|
|
112
|
+
enabled: true,
|
|
113
|
+
removeOnComplete: true,
|
|
114
|
+
phases: {
|
|
115
|
+
queued: '⏳',
|
|
116
|
+
thinking: '🤔',
|
|
117
|
+
toolUse: '⚙️',
|
|
118
|
+
streaming: '✍️',
|
|
119
|
+
done: '✅',
|
|
120
|
+
error: '❌',
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
export let DISCORD_ACK_REACTION = '👀';
|
|
124
|
+
export let DISCORD_ACK_REACTION_SCOPE: RuntimeConfig['discord']['ackReactionScope'] =
|
|
125
|
+
'group-mentions';
|
|
126
|
+
export let DISCORD_REMOVE_ACK_AFTER_REPLY = true;
|
|
127
|
+
export let DISCORD_DEBOUNCE_MS = 2_500;
|
|
128
|
+
export let DISCORD_RATE_LIMIT_PER_USER = 0;
|
|
129
|
+
export let DISCORD_RATE_LIMIT_EXEMPT_ROLES: string[] = [];
|
|
130
|
+
export let DISCORD_SUPPRESS_PATTERNS: string[] = [
|
|
131
|
+
'/stop',
|
|
132
|
+
'/pause',
|
|
133
|
+
'brb',
|
|
134
|
+
'afk',
|
|
135
|
+
];
|
|
136
|
+
export let DISCORD_MAX_CONCURRENT_PER_CHANNEL = 2;
|
|
67
137
|
export let DISCORD_GUILDS: RuntimeConfig['discord']['guilds'] = {};
|
|
68
138
|
|
|
69
139
|
export let HYBRIDAI_BASE_URL = 'https://hybridai.one';
|
|
70
140
|
export let HYBRIDAI_MODEL = 'gpt-5-nano';
|
|
71
141
|
export let HYBRIDAI_CHATBOT_ID = '';
|
|
142
|
+
export let HYBRIDAI_MAX_TOKENS = 4_096;
|
|
72
143
|
export let HYBRIDAI_ENABLE_RAG = true;
|
|
73
144
|
export let HYBRIDAI_MODELS: string[] = ['gpt-5-nano', 'gpt-5-mini', 'gpt-5'];
|
|
74
145
|
|
|
@@ -78,7 +149,10 @@ export let CONTAINER_CPUS = '1';
|
|
|
78
149
|
export let CONTAINER_TIMEOUT = 300_000;
|
|
79
150
|
|
|
80
151
|
export const MOUNT_ALLOWLIST_PATH = path.join(
|
|
81
|
-
os.homedir(),
|
|
152
|
+
os.homedir(),
|
|
153
|
+
'.config',
|
|
154
|
+
'hybridclaw',
|
|
155
|
+
'mount-allowlist.json',
|
|
82
156
|
);
|
|
83
157
|
export let ADDITIONAL_MOUNTS = '';
|
|
84
158
|
|
|
@@ -88,6 +162,8 @@ export let MAX_CONCURRENT_CONTAINERS = 5;
|
|
|
88
162
|
export let HEARTBEAT_ENABLED = true;
|
|
89
163
|
export let HEARTBEAT_INTERVAL = 1_800_000;
|
|
90
164
|
export let HEARTBEAT_CHANNEL = '';
|
|
165
|
+
export let MEMORY_DECAY_RATE = 0.1;
|
|
166
|
+
export let MEMORY_CONSOLIDATION_INTERVAL_HOURS = 24;
|
|
91
167
|
|
|
92
168
|
export let HEALTH_HOST = '127.0.0.1';
|
|
93
169
|
export let HEALTH_PORT = 9090;
|
|
@@ -100,7 +176,8 @@ export let DATA_DIR = path.dirname(DB_PATH);
|
|
|
100
176
|
|
|
101
177
|
export let OBSERVABILITY_ENABLED = true;
|
|
102
178
|
export let OBSERVABILITY_BASE_URL = 'https://hybridai.one';
|
|
103
|
-
export let OBSERVABILITY_INGEST_PATH =
|
|
179
|
+
export let OBSERVABILITY_INGEST_PATH =
|
|
180
|
+
'/api/v1/agent-observability/events:batch';
|
|
104
181
|
export let OBSERVABILITY_STATUS_PATH = '/api/v1/agent-observability/status';
|
|
105
182
|
export let OBSERVABILITY_BOT_ID = '';
|
|
106
183
|
export let OBSERVABILITY_AGENT_ID = 'agent_main';
|
|
@@ -110,7 +187,9 @@ export let OBSERVABILITY_FLUSH_INTERVAL_MS = 10_000;
|
|
|
110
187
|
export let OBSERVABILITY_BATCH_MAX_EVENTS = 500;
|
|
111
188
|
|
|
112
189
|
export let SESSION_COMPACTION_ENABLED = true;
|
|
113
|
-
export let
|
|
190
|
+
export let SESSION_COMPACTION_TOKEN_BUDGET = 100_000;
|
|
191
|
+
export let SESSION_COMPACTION_BUDGET_RATIO = 0.7;
|
|
192
|
+
export let SESSION_COMPACTION_THRESHOLD = 200;
|
|
114
193
|
export let SESSION_COMPACTION_KEEP_RECENT = 40;
|
|
115
194
|
export let SESSION_COMPACTION_SUMMARY_MAX_CHARS = 8_000;
|
|
116
195
|
export let PRE_COMPACTION_MEMORY_FLUSH_ENABLED = true;
|
|
@@ -140,14 +219,51 @@ function applyRuntimeConfig(config: RuntimeConfig): void {
|
|
|
140
219
|
DISCORD_PRESENCE_INTENT = config.discord.presenceIntent;
|
|
141
220
|
DISCORD_RESPOND_TO_ALL_MESSAGES = config.discord.respondToAllMessages;
|
|
142
221
|
DISCORD_COMMANDS_ONLY = config.discord.commandsOnly;
|
|
222
|
+
DISCORD_COMMAND_MODE = config.discord.commandMode;
|
|
223
|
+
DISCORD_COMMAND_ALLOWED_USER_IDS = [...config.discord.commandAllowedUserIds];
|
|
143
224
|
DISCORD_COMMAND_USER_ID = config.discord.commandUserId;
|
|
144
225
|
DISCORD_GROUP_POLICY = config.discord.groupPolicy;
|
|
145
226
|
DISCORD_FREE_RESPONSE_CHANNELS = [...config.discord.freeResponseChannels];
|
|
146
|
-
|
|
227
|
+
DISCORD_TEXT_CHUNK_LIMIT = Math.max(
|
|
228
|
+
200,
|
|
229
|
+
Math.min(2_000, config.discord.textChunkLimit),
|
|
230
|
+
);
|
|
231
|
+
DISCORD_MAX_LINES_PER_MESSAGE = Math.max(
|
|
232
|
+
4,
|
|
233
|
+
Math.min(200, config.discord.maxLinesPerMessage),
|
|
234
|
+
);
|
|
235
|
+
DISCORD_HUMAN_DELAY = JSON.parse(
|
|
236
|
+
JSON.stringify(config.discord.humanDelay),
|
|
237
|
+
) as RuntimeConfig['discord']['humanDelay'];
|
|
238
|
+
DISCORD_TYPING_MODE = config.discord.typingMode;
|
|
239
|
+
DISCORD_SELF_PRESENCE = JSON.parse(
|
|
240
|
+
JSON.stringify(config.discord.presence),
|
|
241
|
+
) as RuntimeConfig['discord']['presence'];
|
|
242
|
+
DISCORD_LIFECYCLE_REACTIONS = JSON.parse(
|
|
243
|
+
JSON.stringify(config.discord.lifecycleReactions),
|
|
244
|
+
) as RuntimeConfig['discord']['lifecycleReactions'];
|
|
245
|
+
DISCORD_ACK_REACTION = config.discord.ackReaction;
|
|
246
|
+
DISCORD_ACK_REACTION_SCOPE = config.discord.ackReactionScope;
|
|
247
|
+
DISCORD_REMOVE_ACK_AFTER_REPLY = config.discord.removeAckAfterReply;
|
|
248
|
+
DISCORD_DEBOUNCE_MS = Math.max(0, config.discord.debounceMs);
|
|
249
|
+
DISCORD_RATE_LIMIT_PER_USER = Math.max(0, config.discord.rateLimitPerUser);
|
|
250
|
+
DISCORD_RATE_LIMIT_EXEMPT_ROLES = [...config.discord.rateLimitExemptRoles];
|
|
251
|
+
DISCORD_SUPPRESS_PATTERNS = [...config.discord.suppressPatterns];
|
|
252
|
+
DISCORD_MAX_CONCURRENT_PER_CHANNEL = Math.max(
|
|
253
|
+
1,
|
|
254
|
+
config.discord.maxConcurrentPerChannel,
|
|
255
|
+
);
|
|
256
|
+
DISCORD_GUILDS = JSON.parse(
|
|
257
|
+
JSON.stringify(config.discord.guilds),
|
|
258
|
+
) as RuntimeConfig['discord']['guilds'];
|
|
147
259
|
|
|
148
260
|
HYBRIDAI_BASE_URL = config.hybridai.baseUrl;
|
|
149
261
|
HYBRIDAI_MODEL = config.hybridai.defaultModel;
|
|
150
262
|
HYBRIDAI_CHATBOT_ID = config.hybridai.defaultChatbotId;
|
|
263
|
+
HYBRIDAI_MAX_TOKENS = Math.max(
|
|
264
|
+
256,
|
|
265
|
+
Math.min(32_768, config.hybridai.maxTokens),
|
|
266
|
+
);
|
|
151
267
|
HYBRIDAI_ENABLE_RAG = config.hybridai.enableRag;
|
|
152
268
|
HYBRIDAI_MODELS = [...config.hybridai.models];
|
|
153
269
|
|
|
@@ -162,16 +278,19 @@ function applyRuntimeConfig(config: RuntimeConfig): void {
|
|
|
162
278
|
HEARTBEAT_ENABLED = config.heartbeat.enabled;
|
|
163
279
|
HEARTBEAT_INTERVAL = config.heartbeat.intervalMs;
|
|
164
280
|
HEARTBEAT_CHANNEL = config.heartbeat.channel;
|
|
281
|
+
MEMORY_DECAY_RATE = config.memory.decayRate;
|
|
282
|
+
MEMORY_CONSOLIDATION_INTERVAL_HOURS =
|
|
283
|
+
config.memory.consolidationIntervalHours;
|
|
165
284
|
|
|
166
285
|
HEALTH_HOST = config.ops.healthHost;
|
|
167
286
|
HEALTH_PORT = config.ops.healthPort;
|
|
168
287
|
WEB_API_TOKEN = process.env.WEB_API_TOKEN || config.ops.webApiToken;
|
|
169
288
|
GATEWAY_BASE_URL = config.ops.gatewayBaseUrl;
|
|
170
289
|
GATEWAY_API_TOKEN =
|
|
171
|
-
process.env.GATEWAY_API_TOKEN
|
|
172
|
-
|
|
173
|
-
||
|
|
174
|
-
|
|
290
|
+
process.env.GATEWAY_API_TOKEN ||
|
|
291
|
+
config.ops.gatewayApiToken ||
|
|
292
|
+
WEB_API_TOKEN ||
|
|
293
|
+
INTERNAL_GATEWAY_API_TOKEN;
|
|
175
294
|
DB_PATH = config.ops.dbPath;
|
|
176
295
|
DATA_DIR = path.dirname(DB_PATH);
|
|
177
296
|
|
|
@@ -183,41 +302,94 @@ function applyRuntimeConfig(config: RuntimeConfig): void {
|
|
|
183
302
|
OBSERVABILITY_AGENT_ID = config.observability.agentId;
|
|
184
303
|
OBSERVABILITY_LABEL = config.observability.label;
|
|
185
304
|
OBSERVABILITY_ENVIRONMENT = config.observability.environment;
|
|
186
|
-
OBSERVABILITY_FLUSH_INTERVAL_MS = Math.max(
|
|
187
|
-
|
|
305
|
+
OBSERVABILITY_FLUSH_INTERVAL_MS = Math.max(
|
|
306
|
+
1_000,
|
|
307
|
+
config.observability.flushIntervalMs,
|
|
308
|
+
);
|
|
309
|
+
OBSERVABILITY_BATCH_MAX_EVENTS = Math.max(
|
|
310
|
+
1,
|
|
311
|
+
Math.min(1_000, config.observability.batchMaxEvents),
|
|
312
|
+
);
|
|
188
313
|
|
|
189
314
|
SESSION_COMPACTION_ENABLED = config.sessionCompaction.enabled;
|
|
190
|
-
|
|
315
|
+
SESSION_COMPACTION_TOKEN_BUDGET = Math.max(
|
|
316
|
+
1_000,
|
|
317
|
+
config.sessionCompaction.tokenBudget,
|
|
318
|
+
);
|
|
319
|
+
SESSION_COMPACTION_BUDGET_RATIO = Math.max(
|
|
320
|
+
0.05,
|
|
321
|
+
Math.min(1, config.sessionCompaction.budgetRatio),
|
|
322
|
+
);
|
|
323
|
+
SESSION_COMPACTION_THRESHOLD = Math.max(
|
|
324
|
+
20,
|
|
325
|
+
config.sessionCompaction.threshold,
|
|
326
|
+
);
|
|
191
327
|
SESSION_COMPACTION_KEEP_RECENT = Math.max(
|
|
192
328
|
1,
|
|
193
|
-
Math.min(
|
|
329
|
+
Math.min(
|
|
330
|
+
config.sessionCompaction.keepRecent,
|
|
331
|
+
SESSION_COMPACTION_THRESHOLD - 1,
|
|
332
|
+
),
|
|
333
|
+
);
|
|
334
|
+
SESSION_COMPACTION_SUMMARY_MAX_CHARS = Math.max(
|
|
335
|
+
1_000,
|
|
336
|
+
config.sessionCompaction.summaryMaxChars,
|
|
337
|
+
);
|
|
338
|
+
PRE_COMPACTION_MEMORY_FLUSH_ENABLED =
|
|
339
|
+
config.sessionCompaction.preCompactionMemoryFlush.enabled;
|
|
340
|
+
PRE_COMPACTION_MEMORY_FLUSH_MAX_MESSAGES = Math.max(
|
|
341
|
+
8,
|
|
342
|
+
config.sessionCompaction.preCompactionMemoryFlush.maxMessages,
|
|
343
|
+
);
|
|
344
|
+
PRE_COMPACTION_MEMORY_FLUSH_MAX_CHARS = Math.max(
|
|
345
|
+
4_000,
|
|
346
|
+
config.sessionCompaction.preCompactionMemoryFlush.maxChars,
|
|
194
347
|
);
|
|
195
|
-
SESSION_COMPACTION_SUMMARY_MAX_CHARS = Math.max(1_000, config.sessionCompaction.summaryMaxChars);
|
|
196
|
-
PRE_COMPACTION_MEMORY_FLUSH_ENABLED = config.sessionCompaction.preCompactionMemoryFlush.enabled;
|
|
197
|
-
PRE_COMPACTION_MEMORY_FLUSH_MAX_MESSAGES = Math.max(8, config.sessionCompaction.preCompactionMemoryFlush.maxMessages);
|
|
198
|
-
PRE_COMPACTION_MEMORY_FLUSH_MAX_CHARS = Math.max(4_000, config.sessionCompaction.preCompactionMemoryFlush.maxChars);
|
|
199
348
|
|
|
200
349
|
PROACTIVE_ACTIVE_HOURS_ENABLED = config.proactive.activeHours.enabled;
|
|
201
350
|
PROACTIVE_ACTIVE_HOURS_TIMEZONE = config.proactive.activeHours.timezone;
|
|
202
|
-
PROACTIVE_ACTIVE_HOURS_START = Math.max(
|
|
203
|
-
|
|
204
|
-
|
|
351
|
+
PROACTIVE_ACTIVE_HOURS_START = Math.max(
|
|
352
|
+
0,
|
|
353
|
+
Math.min(23, config.proactive.activeHours.startHour),
|
|
354
|
+
);
|
|
355
|
+
PROACTIVE_ACTIVE_HOURS_END = Math.max(
|
|
356
|
+
0,
|
|
357
|
+
Math.min(23, config.proactive.activeHours.endHour),
|
|
358
|
+
);
|
|
359
|
+
PROACTIVE_QUEUE_OUTSIDE_HOURS =
|
|
360
|
+
config.proactive.activeHours.queueOutsideHours;
|
|
205
361
|
|
|
206
362
|
PROACTIVE_DELEGATION_ENABLED = config.proactive.delegation.enabled;
|
|
207
|
-
PROACTIVE_DELEGATION_MAX_CONCURRENT = Math.max(
|
|
208
|
-
|
|
209
|
-
|
|
363
|
+
PROACTIVE_DELEGATION_MAX_CONCURRENT = Math.max(
|
|
364
|
+
1,
|
|
365
|
+
config.proactive.delegation.maxConcurrent,
|
|
366
|
+
);
|
|
367
|
+
PROACTIVE_DELEGATION_MAX_DEPTH = Math.max(
|
|
368
|
+
1,
|
|
369
|
+
config.proactive.delegation.maxDepth,
|
|
370
|
+
);
|
|
371
|
+
PROACTIVE_DELEGATION_MAX_PER_TURN = Math.max(
|
|
372
|
+
1,
|
|
373
|
+
config.proactive.delegation.maxPerTurn,
|
|
374
|
+
);
|
|
210
375
|
|
|
211
376
|
PROACTIVE_AUTO_RETRY_ENABLED = config.proactive.autoRetry.enabled;
|
|
212
|
-
PROACTIVE_AUTO_RETRY_MAX_ATTEMPTS = Math.max(
|
|
213
|
-
|
|
377
|
+
PROACTIVE_AUTO_RETRY_MAX_ATTEMPTS = Math.max(
|
|
378
|
+
1,
|
|
379
|
+
config.proactive.autoRetry.maxAttempts,
|
|
380
|
+
);
|
|
381
|
+
PROACTIVE_AUTO_RETRY_BASE_DELAY_MS = Math.max(
|
|
382
|
+
100,
|
|
383
|
+
config.proactive.autoRetry.baseDelayMs,
|
|
384
|
+
);
|
|
214
385
|
PROACTIVE_AUTO_RETRY_MAX_DELAY_MS = Math.max(
|
|
215
386
|
PROACTIVE_AUTO_RETRY_BASE_DELAY_MS,
|
|
216
387
|
config.proactive.autoRetry.maxDelayMs,
|
|
217
388
|
);
|
|
218
389
|
|
|
219
390
|
const rawRalphMax = Math.trunc(config.proactive.ralph.maxIterations);
|
|
220
|
-
PROACTIVE_RALPH_MAX_ITERATIONS =
|
|
391
|
+
PROACTIVE_RALPH_MAX_ITERATIONS =
|
|
392
|
+
rawRalphMax === -1 ? -1 : Math.max(0, rawRalphMax);
|
|
221
393
|
}
|
|
222
394
|
|
|
223
395
|
applyRuntimeConfig(getRuntimeConfig());
|
package/src/container-runner.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Container Runner — manages a pool of persistent containers.
|
|
3
3
|
* Containers stay alive between requests and exit after an idle timeout.
|
|
4
4
|
*/
|
|
5
|
-
import { ChildProcess, spawn } from 'child_process';
|
|
5
|
+
import { type ChildProcess, spawn } from 'child_process';
|
|
6
6
|
import fs from 'fs';
|
|
7
7
|
import path from 'path';
|
|
8
8
|
|
|
@@ -15,7 +15,9 @@ import {
|
|
|
15
15
|
DATA_DIR,
|
|
16
16
|
GATEWAY_API_TOKEN,
|
|
17
17
|
GATEWAY_BASE_URL,
|
|
18
|
+
getHybridAIApiKey,
|
|
18
19
|
HYBRIDAI_BASE_URL,
|
|
20
|
+
HYBRIDAI_MAX_TOKENS,
|
|
19
21
|
HYBRIDAI_MODEL,
|
|
20
22
|
MAX_CONCURRENT_CONTAINERS,
|
|
21
23
|
PROACTIVE_AUTO_RETRY_BASE_DELAY_MS,
|
|
@@ -23,9 +25,15 @@ import {
|
|
|
23
25
|
PROACTIVE_AUTO_RETRY_MAX_ATTEMPTS,
|
|
24
26
|
PROACTIVE_AUTO_RETRY_MAX_DELAY_MS,
|
|
25
27
|
PROACTIVE_RALPH_MAX_ITERATIONS,
|
|
26
|
-
getHybridAIApiKey,
|
|
27
28
|
} from './config.js';
|
|
28
|
-
import {
|
|
29
|
+
import {
|
|
30
|
+
cleanupIpc,
|
|
31
|
+
ensureAgentDirs,
|
|
32
|
+
ensureSessionDirs,
|
|
33
|
+
getSessionPaths,
|
|
34
|
+
readOutput,
|
|
35
|
+
writeInput,
|
|
36
|
+
} from './ipc.js';
|
|
29
37
|
import { logger } from './logger.js';
|
|
30
38
|
import { validateAdditionalMounts } from './mount-security.js';
|
|
31
39
|
import type {
|
|
@@ -52,7 +60,8 @@ interface PoolEntry {
|
|
|
52
60
|
}
|
|
53
61
|
|
|
54
62
|
const pool = new Map<string, PoolEntry>();
|
|
55
|
-
const TOOL_RESULT_RE =
|
|
63
|
+
const TOOL_RESULT_RE =
|
|
64
|
+
/^\[tool\]\s+([a-zA-Z0-9_.-]+)\s+result\s+\((\d+)ms\):\s*(.*)$/;
|
|
56
65
|
const TOOL_START_RE = /^\[tool\]\s+([a-zA-Z0-9_.-]+):\s*(.*)$/;
|
|
57
66
|
const STREAM_DELTA_RE = /^\[stream\]\s+([A-Za-z0-9+/=]+)$/;
|
|
58
67
|
const CONTAINER_WORKSPACE_ROOT = '/workspace';
|
|
@@ -73,7 +82,10 @@ function emitTextDelta(entry: PoolEntry, line: string): void {
|
|
|
73
82
|
if (!delta) return;
|
|
74
83
|
callback(delta);
|
|
75
84
|
} catch (err) {
|
|
76
|
-
logger.debug(
|
|
85
|
+
logger.debug(
|
|
86
|
+
{ sessionId: entry.sessionId, err },
|
|
87
|
+
'Text delta callback failed',
|
|
88
|
+
);
|
|
77
89
|
}
|
|
78
90
|
}
|
|
79
91
|
|
|
@@ -92,7 +104,10 @@ function emitToolProgress(entry: PoolEntry, line: string): void {
|
|
|
92
104
|
preview: resultMatch[3],
|
|
93
105
|
});
|
|
94
106
|
} catch (err) {
|
|
95
|
-
logger.debug(
|
|
107
|
+
logger.debug(
|
|
108
|
+
{ sessionId: entry.sessionId, err },
|
|
109
|
+
'Tool progress callback failed',
|
|
110
|
+
);
|
|
96
111
|
}
|
|
97
112
|
return;
|
|
98
113
|
}
|
|
@@ -107,7 +122,10 @@ function emitToolProgress(entry: PoolEntry, line: string): void {
|
|
|
107
122
|
preview: startMatch[2],
|
|
108
123
|
});
|
|
109
124
|
} catch (err) {
|
|
110
|
-
logger.debug(
|
|
125
|
+
logger.debug(
|
|
126
|
+
{ sessionId: entry.sessionId, err },
|
|
127
|
+
'Tool progress callback failed',
|
|
128
|
+
);
|
|
111
129
|
}
|
|
112
130
|
}
|
|
113
131
|
}
|
|
@@ -119,7 +137,10 @@ export function getActiveContainerCount(): number {
|
|
|
119
137
|
export function stopSessionContainer(sessionId: string): boolean {
|
|
120
138
|
const entry = pool.get(sessionId);
|
|
121
139
|
if (!entry) return false;
|
|
122
|
-
logger.info(
|
|
140
|
+
logger.info(
|
|
141
|
+
{ sessionId, containerName: entry.containerName },
|
|
142
|
+
'Stopping session container',
|
|
143
|
+
);
|
|
123
144
|
stopContainer(entry.containerName);
|
|
124
145
|
pool.delete(sessionId);
|
|
125
146
|
return true;
|
|
@@ -132,7 +153,10 @@ function stopContainer(containerName: string): void {
|
|
|
132
153
|
});
|
|
133
154
|
}
|
|
134
155
|
|
|
135
|
-
function resolveArtifactHostPath(
|
|
156
|
+
function resolveArtifactHostPath(
|
|
157
|
+
rawPath: string,
|
|
158
|
+
workspacePath: string,
|
|
159
|
+
): string | null {
|
|
136
160
|
const input = String(rawPath || '').trim();
|
|
137
161
|
if (!input) return null;
|
|
138
162
|
const normalized = input.replace(/\\/g, '/');
|
|
@@ -140,12 +164,20 @@ function resolveArtifactHostPath(rawPath: string, workspacePath: string): string
|
|
|
140
164
|
|
|
141
165
|
if (path.posix.isAbsolute(normalized)) {
|
|
142
166
|
const cleanAbs = path.posix.normalize(normalized);
|
|
143
|
-
if (
|
|
167
|
+
if (
|
|
168
|
+
cleanAbs !== CONTAINER_WORKSPACE_ROOT &&
|
|
169
|
+
!cleanAbs.startsWith(`${CONTAINER_WORKSPACE_ROOT}/`)
|
|
170
|
+
) {
|
|
144
171
|
return null;
|
|
145
172
|
}
|
|
146
|
-
const rel = cleanAbs
|
|
173
|
+
const rel = cleanAbs
|
|
174
|
+
.slice(CONTAINER_WORKSPACE_ROOT.length)
|
|
175
|
+
.replace(/^\/+/, '');
|
|
147
176
|
const resolved = path.resolve(workspaceRoot, rel);
|
|
148
|
-
if (
|
|
177
|
+
if (
|
|
178
|
+
resolved === workspaceRoot ||
|
|
179
|
+
resolved.startsWith(`${workspaceRoot}${path.sep}`)
|
|
180
|
+
) {
|
|
149
181
|
return resolved;
|
|
150
182
|
}
|
|
151
183
|
return null;
|
|
@@ -154,21 +186,32 @@ function resolveArtifactHostPath(rawPath: string, workspacePath: string): string
|
|
|
154
186
|
const cleanRel = path.posix.normalize(normalized);
|
|
155
187
|
if (cleanRel === '..' || cleanRel.startsWith('../')) return null;
|
|
156
188
|
const resolved = path.resolve(workspaceRoot, cleanRel);
|
|
157
|
-
if (
|
|
189
|
+
if (
|
|
190
|
+
resolved === workspaceRoot ||
|
|
191
|
+
resolved.startsWith(`${workspaceRoot}${path.sep}`)
|
|
192
|
+
) {
|
|
158
193
|
return resolved;
|
|
159
194
|
}
|
|
160
195
|
return null;
|
|
161
196
|
}
|
|
162
197
|
|
|
163
|
-
function remapOutputArtifacts(
|
|
198
|
+
function remapOutputArtifacts(
|
|
199
|
+
output: ContainerOutput,
|
|
200
|
+
workspacePath: string,
|
|
201
|
+
): void {
|
|
164
202
|
if (!Array.isArray(output.artifacts) || output.artifacts.length === 0) return;
|
|
165
203
|
const mapped: ArtifactMetadata[] = [];
|
|
166
204
|
for (const artifact of output.artifacts) {
|
|
167
205
|
const raw = artifact as Partial<ArtifactMetadata>;
|
|
168
|
-
const hostPath = resolveArtifactHostPath(
|
|
206
|
+
const hostPath = resolveArtifactHostPath(
|
|
207
|
+
String(raw.path || ''),
|
|
208
|
+
workspacePath,
|
|
209
|
+
);
|
|
169
210
|
if (!hostPath) continue;
|
|
170
|
-
const filename =
|
|
171
|
-
|
|
211
|
+
const filename =
|
|
212
|
+
String(raw.filename || '').trim() || path.basename(hostPath);
|
|
213
|
+
const mimeType =
|
|
214
|
+
String(raw.mimeType || '').trim() || 'application/octet-stream';
|
|
172
215
|
mapped.push({ path: hostPath, filename, mimeType });
|
|
173
216
|
}
|
|
174
217
|
if (mapped.length === 0) {
|
|
@@ -179,7 +222,10 @@ function remapOutputArtifacts(output: ContainerOutput, workspacePath: string): v
|
|
|
179
222
|
}
|
|
180
223
|
|
|
181
224
|
function remapHostBaseUrlForContainer(baseUrl: string): string {
|
|
182
|
-
return baseUrl.replace(
|
|
225
|
+
return baseUrl.replace(
|
|
226
|
+
/\/\/(localhost|127\.0\.0\.1)([:/])/,
|
|
227
|
+
'//host.docker.internal$2',
|
|
228
|
+
);
|
|
183
229
|
}
|
|
184
230
|
|
|
185
231
|
/**
|
|
@@ -187,8 +233,15 @@ function remapHostBaseUrlForContainer(baseUrl: string): string {
|
|
|
187
233
|
*/
|
|
188
234
|
function getOrSpawnContainer(sessionId: string, agentId: string): PoolEntry {
|
|
189
235
|
const existing = pool.get(sessionId);
|
|
190
|
-
if (
|
|
191
|
-
|
|
236
|
+
if (
|
|
237
|
+
existing &&
|
|
238
|
+
!existing.process.killed &&
|
|
239
|
+
existing.process.exitCode === null
|
|
240
|
+
) {
|
|
241
|
+
logger.debug(
|
|
242
|
+
{ sessionId, containerName: existing.containerName },
|
|
243
|
+
'Reusing container',
|
|
244
|
+
);
|
|
192
245
|
return existing;
|
|
193
246
|
}
|
|
194
247
|
|
|
@@ -208,23 +261,38 @@ function getOrSpawnContainer(sessionId: string, agentId: string): PoolEntry {
|
|
|
208
261
|
'run',
|
|
209
262
|
'--rm',
|
|
210
263
|
'-i',
|
|
211
|
-
'--name',
|
|
212
|
-
|
|
264
|
+
'--name',
|
|
265
|
+
containerName,
|
|
266
|
+
'--memory',
|
|
267
|
+
CONTAINER_MEMORY,
|
|
213
268
|
`--cpus=${CONTAINER_CPUS}`,
|
|
214
269
|
'--read-only',
|
|
215
|
-
'--tmpfs',
|
|
216
|
-
'
|
|
217
|
-
'-v',
|
|
218
|
-
|
|
219
|
-
'-
|
|
220
|
-
|
|
221
|
-
'-
|
|
222
|
-
|
|
223
|
-
'-e',
|
|
224
|
-
|
|
225
|
-
'-e',
|
|
226
|
-
|
|
227
|
-
'-e',
|
|
270
|
+
'--tmpfs',
|
|
271
|
+
'/tmp',
|
|
272
|
+
'-v',
|
|
273
|
+
`${workspacePath}:/workspace:rw`,
|
|
274
|
+
'-v',
|
|
275
|
+
`${ipcPath}:/ipc:rw`,
|
|
276
|
+
'-v',
|
|
277
|
+
`${mediaCacheHostPath}:${CONTAINER_DISCORD_MEDIA_CACHE_ROOT}:ro`,
|
|
278
|
+
'-e',
|
|
279
|
+
`HYBRIDAI_BASE_URL=${HYBRIDAI_BASE_URL}`,
|
|
280
|
+
'-e',
|
|
281
|
+
`HYBRIDAI_MODEL=${HYBRIDAI_MODEL}`,
|
|
282
|
+
'-e',
|
|
283
|
+
`CONTAINER_IDLE_TIMEOUT=${IDLE_TIMEOUT_MS}`,
|
|
284
|
+
'-e',
|
|
285
|
+
`HYBRIDCLAW_RETRY_ENABLED=${PROACTIVE_AUTO_RETRY_ENABLED ? 'true' : 'false'}`,
|
|
286
|
+
'-e',
|
|
287
|
+
`HYBRIDCLAW_RETRY_MAX_ATTEMPTS=${PROACTIVE_AUTO_RETRY_MAX_ATTEMPTS}`,
|
|
288
|
+
'-e',
|
|
289
|
+
`HYBRIDCLAW_RETRY_BASE_DELAY_MS=${PROACTIVE_AUTO_RETRY_BASE_DELAY_MS}`,
|
|
290
|
+
'-e',
|
|
291
|
+
`HYBRIDCLAW_RETRY_MAX_DELAY_MS=${PROACTIVE_AUTO_RETRY_MAX_DELAY_MS}`,
|
|
292
|
+
'-e',
|
|
293
|
+
`HYBRIDCLAW_RALPH_MAX_ITERATIONS=${PROACTIVE_RALPH_MAX_ITERATIONS}`,
|
|
294
|
+
'-e',
|
|
295
|
+
'PLAYWRIGHT_BROWSERS_PATH=/ms-playwright',
|
|
228
296
|
];
|
|
229
297
|
|
|
230
298
|
// Run as host user so bind-mount file ownership matches
|
|
@@ -241,10 +309,16 @@ function getOrSpawnContainer(sessionId: string, agentId: string): PoolEntry {
|
|
|
241
309
|
const requested = JSON.parse(ADDITIONAL_MOUNTS) as AdditionalMount[];
|
|
242
310
|
const validated = validateAdditionalMounts(requested);
|
|
243
311
|
for (const m of validated) {
|
|
244
|
-
args.push(
|
|
312
|
+
args.push(
|
|
313
|
+
'-v',
|
|
314
|
+
`${m.hostPath}:${m.containerPath}:${m.readonly ? 'ro' : 'rw'}`,
|
|
315
|
+
);
|
|
245
316
|
}
|
|
246
317
|
} catch (err) {
|
|
247
|
-
logger.warn(
|
|
318
|
+
logger.warn(
|
|
319
|
+
{ error: err instanceof Error ? err.message : String(err) },
|
|
320
|
+
'Failed to parse ADDITIONAL_MOUNTS',
|
|
321
|
+
);
|
|
248
322
|
}
|
|
249
323
|
}
|
|
250
324
|
|
|
@@ -334,7 +408,10 @@ export async function runContainer(
|
|
|
334
408
|
cleanupIpc(sessionId);
|
|
335
409
|
ensureSessionDirs(sessionId);
|
|
336
410
|
|
|
337
|
-
const isNewContainer =
|
|
411
|
+
const isNewContainer =
|
|
412
|
+
!pool.has(sessionId) ||
|
|
413
|
+
pool.get(sessionId)!.process.killed ||
|
|
414
|
+
pool.get(sessionId)!.process.exitCode !== null;
|
|
338
415
|
|
|
339
416
|
let entry: PoolEntry;
|
|
340
417
|
try {
|
|
@@ -358,6 +435,7 @@ export async function runContainer(
|
|
|
358
435
|
gatewayBaseUrl: remapHostBaseUrlForContainer(GATEWAY_BASE_URL),
|
|
359
436
|
gatewayApiToken: GATEWAY_API_TOKEN || undefined,
|
|
360
437
|
model,
|
|
438
|
+
maxTokens: HYBRIDAI_MAX_TOKENS,
|
|
361
439
|
channelId,
|
|
362
440
|
scheduledTasks: scheduledTasks?.map((t) => ({
|
|
363
441
|
id: t.id,
|
|
@@ -377,7 +455,10 @@ export async function runContainer(
|
|
|
377
455
|
entry.onTextDelta = onTextDelta;
|
|
378
456
|
entry.onToolProgress = onToolProgress;
|
|
379
457
|
const onAbort = () => {
|
|
380
|
-
logger.info(
|
|
458
|
+
logger.info(
|
|
459
|
+
{ sessionId, containerName: entry.containerName },
|
|
460
|
+
'Interrupt requested, stopping container',
|
|
461
|
+
);
|
|
381
462
|
stopContainer(entry.containerName);
|
|
382
463
|
};
|
|
383
464
|
if (abortSignal) {
|
|
@@ -397,12 +478,20 @@ export async function runContainer(
|
|
|
397
478
|
}
|
|
398
479
|
|
|
399
480
|
// Wait for the container to produce output
|
|
400
|
-
const output = await readOutput(sessionId, CONTAINER_TIMEOUT, {
|
|
481
|
+
const output = await readOutput(sessionId, CONTAINER_TIMEOUT, {
|
|
482
|
+
signal: abortSignal,
|
|
483
|
+
});
|
|
401
484
|
remapOutputArtifacts(output, workspacePath);
|
|
402
485
|
const duration = Date.now() - startTime;
|
|
403
486
|
|
|
404
487
|
logger.info(
|
|
405
|
-
{
|
|
488
|
+
{
|
|
489
|
+
sessionId,
|
|
490
|
+
containerName: entry.containerName,
|
|
491
|
+
duration,
|
|
492
|
+
status: output.status,
|
|
493
|
+
toolsUsed: output.toolsUsed,
|
|
494
|
+
},
|
|
406
495
|
'Request completed',
|
|
407
496
|
);
|
|
408
497
|
|
|
@@ -423,7 +512,10 @@ export async function runContainer(
|
|
|
423
512
|
*/
|
|
424
513
|
export function stopAllContainers(): void {
|
|
425
514
|
for (const [sessionId, entry] of pool) {
|
|
426
|
-
logger.info(
|
|
515
|
+
logger.info(
|
|
516
|
+
{ sessionId, containerName: entry.containerName },
|
|
517
|
+
'Stopping container (shutdown)',
|
|
518
|
+
);
|
|
427
519
|
stopContainer(entry.containerName);
|
|
428
520
|
}
|
|
429
521
|
pool.clear();
|