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