@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/onboarding.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
1
2
|
import fs from 'fs';
|
|
2
3
|
import path from 'path';
|
|
3
4
|
import readline from 'readline/promises';
|
|
4
|
-
import { spawn } from 'child_process';
|
|
5
5
|
|
|
6
6
|
import { loadEnvFile } from './env.js';
|
|
7
7
|
import {
|
|
@@ -64,7 +64,10 @@ function inferThemeFromColorFgBg(): TerminalTheme | null {
|
|
|
64
64
|
const raw = process.env.COLORFGBG;
|
|
65
65
|
if (!raw) return null;
|
|
66
66
|
|
|
67
|
-
const parts = raw
|
|
67
|
+
const parts = raw
|
|
68
|
+
.split(/[;:]/)
|
|
69
|
+
.map((part) => part.trim())
|
|
70
|
+
.filter(Boolean);
|
|
68
71
|
if (parts.length === 0) return null;
|
|
69
72
|
|
|
70
73
|
const bg = Number.parseInt(parts[parts.length - 1], 10);
|
|
@@ -75,12 +78,20 @@ function inferThemeFromColorFgBg(): TerminalTheme | null {
|
|
|
75
78
|
}
|
|
76
79
|
|
|
77
80
|
function resolveOnboardingTheme(): TerminalTheme {
|
|
78
|
-
const override = (
|
|
81
|
+
const override = (
|
|
82
|
+
process.env.HYBRIDCLAW_THEME ||
|
|
83
|
+
process.env.HYBRIDCLAW_TUI_THEME ||
|
|
84
|
+
process.env.TUI_THEME ||
|
|
85
|
+
''
|
|
86
|
+
)
|
|
87
|
+
.trim()
|
|
88
|
+
.toLowerCase();
|
|
79
89
|
if (override === 'light' || override === 'dark') return override;
|
|
80
90
|
return inferThemeFromColorFgBg() || 'dark';
|
|
81
91
|
}
|
|
82
92
|
|
|
83
|
-
const PALETTE =
|
|
93
|
+
const PALETTE =
|
|
94
|
+
resolveOnboardingTheme() === 'light' ? LIGHT_PALETTE : DARK_PALETTE;
|
|
84
95
|
const MUTED = PALETTE.muted;
|
|
85
96
|
const TEAL = PALETTE.teal;
|
|
86
97
|
const GOLD = PALETTE.gold;
|
|
@@ -135,11 +146,14 @@ function parseErrorMessage(payload: unknown, fallback: string): string {
|
|
|
135
146
|
if (typeof payload !== 'object') return fallback;
|
|
136
147
|
|
|
137
148
|
const asRecord = payload as Record<string, unknown>;
|
|
138
|
-
if (typeof asRecord.message === 'string' && asRecord.message.trim())
|
|
139
|
-
|
|
149
|
+
if (typeof asRecord.message === 'string' && asRecord.message.trim())
|
|
150
|
+
return asRecord.message;
|
|
151
|
+
if (typeof asRecord.error === 'string' && asRecord.error.trim())
|
|
152
|
+
return asRecord.error;
|
|
140
153
|
if (asRecord.error && typeof asRecord.error === 'object') {
|
|
141
154
|
const nested = asRecord.error as Record<string, unknown>;
|
|
142
|
-
if (typeof nested.message === 'string' && nested.message.trim())
|
|
155
|
+
if (typeof nested.message === 'string' && nested.message.trim())
|
|
156
|
+
return nested.message;
|
|
143
157
|
}
|
|
144
158
|
|
|
145
159
|
return fallback;
|
|
@@ -191,7 +205,8 @@ function extractApiKeyFromInput(raw: string): string | null {
|
|
|
191
205
|
|
|
192
206
|
function getOpenCommand(url: string): { cmd: string; args: string[] } | null {
|
|
193
207
|
if (process.platform === 'darwin') return { cmd: 'open', args: [url] };
|
|
194
|
-
if (process.platform === 'win32')
|
|
208
|
+
if (process.platform === 'win32')
|
|
209
|
+
return { cmd: 'cmd', args: ['/c', 'start', '', url] };
|
|
195
210
|
if (process.platform === 'linux') return { cmd: 'xdg-open', args: [url] };
|
|
196
211
|
return null;
|
|
197
212
|
}
|
|
@@ -215,20 +230,30 @@ async function tryOpenUrl(url: string): Promise<boolean> {
|
|
|
215
230
|
|
|
216
231
|
function normalizeBots(payload: unknown): HybridAIBot[] {
|
|
217
232
|
const data = payload as
|
|
218
|
-
| {
|
|
233
|
+
| {
|
|
234
|
+
data?: Record<string, unknown>[];
|
|
235
|
+
bots?: Record<string, unknown>[];
|
|
236
|
+
items?: Record<string, unknown>[];
|
|
237
|
+
}
|
|
219
238
|
| Record<string, unknown>[];
|
|
220
|
-
const raw = Array.isArray(data)
|
|
239
|
+
const raw = Array.isArray(data)
|
|
240
|
+
? data
|
|
241
|
+
: data?.data || data?.bots || data?.items || [];
|
|
221
242
|
|
|
222
243
|
return raw
|
|
223
244
|
.map((item) => ({
|
|
224
245
|
id: String(item.id ?? item._id ?? item.chatbot_id ?? item.bot_id ?? ''),
|
|
225
246
|
name: String(item.bot_name ?? item.name ?? 'Unnamed'),
|
|
226
|
-
description:
|
|
247
|
+
description:
|
|
248
|
+
item.description != null ? String(item.description) : undefined,
|
|
227
249
|
}))
|
|
228
250
|
.filter((bot) => Boolean(bot.id));
|
|
229
251
|
}
|
|
230
252
|
|
|
231
|
-
async function validateApiKey(
|
|
253
|
+
async function validateApiKey(
|
|
254
|
+
baseUrl: string,
|
|
255
|
+
apiKey: string,
|
|
256
|
+
): Promise<ApiKeyValidationResult> {
|
|
232
257
|
let response: Response;
|
|
233
258
|
try {
|
|
234
259
|
response = await fetch(resolveUrl(baseUrl, BOT_LIST_PATH), {
|
|
@@ -247,7 +272,10 @@ async function validateApiKey(baseUrl: string, apiKey: string): Promise<ApiKeyVa
|
|
|
247
272
|
return {
|
|
248
273
|
ok: false,
|
|
249
274
|
bots: [],
|
|
250
|
-
error: parseErrorMessage(
|
|
275
|
+
error: parseErrorMessage(
|
|
276
|
+
payload,
|
|
277
|
+
`Validation failed with HTTP ${response.status}.`,
|
|
278
|
+
),
|
|
251
279
|
};
|
|
252
280
|
}
|
|
253
281
|
|
|
@@ -285,7 +313,9 @@ function removeEnvLine(content: string, key: string): string {
|
|
|
285
313
|
|
|
286
314
|
function saveEnvCredentials(apiKey: string): void {
|
|
287
315
|
const envPath = path.join(process.cwd(), '.env');
|
|
288
|
-
const existing = fs.existsSync(envPath)
|
|
316
|
+
const existing = fs.existsSync(envPath)
|
|
317
|
+
? fs.readFileSync(envPath, 'utf-8')
|
|
318
|
+
: '';
|
|
289
319
|
|
|
290
320
|
let updated = upsertEnvLine(existing, 'HYBRIDAI_API_KEY', apiKey);
|
|
291
321
|
updated = removeEnvLine(updated, 'HYBRIDAI_CHATBOT_ID');
|
|
@@ -345,7 +375,9 @@ async function promptRequired(
|
|
|
345
375
|
icon = ICON_PROMPT,
|
|
346
376
|
): Promise<string> {
|
|
347
377
|
while (true) {
|
|
348
|
-
const value = (
|
|
378
|
+
const value = (
|
|
379
|
+
await rl.question(styledPromptWithIcon(question, icon))
|
|
380
|
+
).trim();
|
|
349
381
|
if (value) return value;
|
|
350
382
|
printWarn('Please enter a value.');
|
|
351
383
|
}
|
|
@@ -366,7 +398,11 @@ async function promptYesNo(
|
|
|
366
398
|
icon = ICON_PROMPT,
|
|
367
399
|
): Promise<boolean> {
|
|
368
400
|
const suffix = defaultYes ? ' [Y/n] ' : ' [y/N] ';
|
|
369
|
-
const raw = (
|
|
401
|
+
const raw = (
|
|
402
|
+
await rl.question(styledPromptWithIcon(`${question}${suffix}`, icon))
|
|
403
|
+
)
|
|
404
|
+
.trim()
|
|
405
|
+
.toLowerCase();
|
|
370
406
|
if (!raw) return defaultYes;
|
|
371
407
|
if (raw === 'y' || raw === 'yes') return true;
|
|
372
408
|
if (raw === 'n' || raw === 'no') return false;
|
|
@@ -390,7 +426,9 @@ async function chooseDefaultBot(
|
|
|
390
426
|
console.log(`${TEAL}${ICON_TITLE}${RESET} Available bots:`);
|
|
391
427
|
for (let i = 0; i < Math.min(10, bots.length); i++) {
|
|
392
428
|
const bot = bots[i];
|
|
393
|
-
console.log(
|
|
429
|
+
console.log(
|
|
430
|
+
`${TEAL}${i + 1}.${RESET} ${bot.name} ${MUTED}(${bot.id})${RESET}`,
|
|
431
|
+
);
|
|
394
432
|
}
|
|
395
433
|
if (bots.length > 10) {
|
|
396
434
|
console.log(`${MUTED}...and ${bots.length - 10} more${RESET}`);
|
|
@@ -424,17 +462,28 @@ async function ensureSecurityTrustAcceptance(
|
|
|
424
462
|
if (isSecurityTrustAccepted(existingConfig) && !force) return false;
|
|
425
463
|
|
|
426
464
|
printHeadline('Security trust model acceptance');
|
|
427
|
-
printInfo(
|
|
465
|
+
printInfo(
|
|
466
|
+
`${commandLabel} requires explicit trust model acceptance before runtime starts.`,
|
|
467
|
+
);
|
|
428
468
|
printMeta('Policy version', SECURITY_POLICY_VERSION);
|
|
429
469
|
printMeta('Current acceptance', formatAcceptanceMeta());
|
|
430
470
|
printLink(`Policy document: ${TRUST_MODEL_DOC_PATH}`);
|
|
431
471
|
printInfo('Review TRUST_MODEL.md before continuing.');
|
|
432
|
-
printInfo(
|
|
472
|
+
printInfo(
|
|
473
|
+
'Acceptance confirms you understand container/tool risks, data handling, and operator responsibilities.',
|
|
474
|
+
);
|
|
433
475
|
console.log();
|
|
434
476
|
|
|
435
|
-
const ready = await promptYesNo(
|
|
477
|
+
const ready = await promptYesNo(
|
|
478
|
+
rl,
|
|
479
|
+
'Have you reviewed TRUST_MODEL.md and the trust model?',
|
|
480
|
+
true,
|
|
481
|
+
ICON_AUTH,
|
|
482
|
+
);
|
|
436
483
|
if (!ready) {
|
|
437
|
-
throw new Error(
|
|
484
|
+
throw new Error(
|
|
485
|
+
'Security trust model acceptance is required. Review TRUST_MODEL.md and rerun onboarding.',
|
|
486
|
+
);
|
|
438
487
|
}
|
|
439
488
|
|
|
440
489
|
while (true) {
|
|
@@ -462,7 +511,9 @@ async function ensureSecurityTrustAcceptance(
|
|
|
462
511
|
return true;
|
|
463
512
|
}
|
|
464
513
|
|
|
465
|
-
export async function ensureHybridAICredentials(
|
|
514
|
+
export async function ensureHybridAICredentials(
|
|
515
|
+
options: OnboardingOptions = {},
|
|
516
|
+
): Promise<void> {
|
|
466
517
|
loadEnvFile();
|
|
467
518
|
const bootstrappedConfig = ensureRuntimeConfigFile();
|
|
468
519
|
|
|
@@ -487,12 +538,19 @@ export async function ensureHybridAICredentials(options: OnboardingOptions = {})
|
|
|
487
538
|
const bootstrappedEnv = ensureEnvFileFromExample();
|
|
488
539
|
if (bootstrappedEnv) loadEnvFile();
|
|
489
540
|
|
|
490
|
-
const baseUrl = normalizeBaseUrl(
|
|
541
|
+
const baseUrl = normalizeBaseUrl(
|
|
542
|
+
getRuntimeConfig().hybridai.baseUrl ||
|
|
543
|
+
process.env.HYBRIDAI_BASE_URL ||
|
|
544
|
+
DEFAULT_BASE_URL,
|
|
545
|
+
);
|
|
491
546
|
const registerPageUrl = resolveUrl(baseUrl, DEFAULT_REGISTER_PATH);
|
|
492
547
|
const loginUrl = resolveUrl(baseUrl, DEFAULT_LOGIN_PATH);
|
|
493
548
|
const commandLabel = options.commandName || 'hybridclaw';
|
|
494
549
|
|
|
495
|
-
const rl = readline.createInterface({
|
|
550
|
+
const rl = readline.createInterface({
|
|
551
|
+
input: process.stdin,
|
|
552
|
+
output: process.stdout,
|
|
553
|
+
});
|
|
496
554
|
|
|
497
555
|
try {
|
|
498
556
|
printHeadline('HybridAI onboarding');
|
|
@@ -505,19 +563,28 @@ export async function ensureHybridAICredentials(options: OnboardingOptions = {})
|
|
|
505
563
|
await ensureSecurityTrustAcceptance(rl, commandLabel, force);
|
|
506
564
|
|
|
507
565
|
if (existingKey && !force) {
|
|
508
|
-
printSuccess(
|
|
566
|
+
printSuccess(
|
|
567
|
+
'Security trust model already accepted and API key is present. No credential changes needed.',
|
|
568
|
+
);
|
|
509
569
|
return;
|
|
510
570
|
}
|
|
511
571
|
|
|
512
572
|
printMeta('HYBRIDAI_BASE_URL', baseUrl);
|
|
513
573
|
if (!existingKey) {
|
|
514
|
-
printInfo(
|
|
574
|
+
printInfo(
|
|
575
|
+
`No HYBRIDAI_API_KEY found. ${commandLabel} needs HybridAI credentials before it can start.`,
|
|
576
|
+
);
|
|
515
577
|
} else {
|
|
516
578
|
printSetup('Reconfiguring HybridAI credentials.');
|
|
517
579
|
}
|
|
518
580
|
console.log();
|
|
519
581
|
|
|
520
|
-
const wantsNewAccount = await promptYesNo(
|
|
582
|
+
const wantsNewAccount = await promptYesNo(
|
|
583
|
+
rl,
|
|
584
|
+
'Create a new HybridAI account now?',
|
|
585
|
+
true,
|
|
586
|
+
ICON_PERSON,
|
|
587
|
+
);
|
|
521
588
|
let email = '';
|
|
522
589
|
|
|
523
590
|
if (wantsNewAccount) {
|
|
@@ -540,10 +607,17 @@ export async function ensureHybridAICredentials(options: OnboardingOptions = {})
|
|
|
540
607
|
ICON_PERSON,
|
|
541
608
|
);
|
|
542
609
|
if (email) {
|
|
543
|
-
const verifyUrl = resolveUrl(
|
|
610
|
+
const verifyUrl = resolveUrl(
|
|
611
|
+
baseUrl,
|
|
612
|
+
`${DEFAULT_VERIFY_PATH}?email=${encodeURIComponent(email)}`,
|
|
613
|
+
);
|
|
544
614
|
printLink(`Verify your email here: ${verifyUrl}`);
|
|
545
615
|
}
|
|
546
|
-
await promptOptional(
|
|
616
|
+
await promptOptional(
|
|
617
|
+
rl,
|
|
618
|
+
'When registration/email verification is done, press Enter...',
|
|
619
|
+
ICON_PERSON,
|
|
620
|
+
);
|
|
547
621
|
console.log();
|
|
548
622
|
}
|
|
549
623
|
|
|
@@ -569,17 +643,31 @@ export async function ensureHybridAICredentials(options: OnboardingOptions = {})
|
|
|
569
643
|
if (pasted) {
|
|
570
644
|
seededApiKey = extractApiKeyFromInput(pasted) || '';
|
|
571
645
|
if (!seededApiKey) {
|
|
572
|
-
printWarn(
|
|
646
|
+
printWarn(
|
|
647
|
+
'Could not extract an API key from input; you can paste the raw key next.',
|
|
648
|
+
);
|
|
573
649
|
}
|
|
574
650
|
} else {
|
|
575
|
-
await promptOptional(
|
|
651
|
+
await promptOptional(
|
|
652
|
+
rl,
|
|
653
|
+
'When login/API key retrieval is done, press Enter...',
|
|
654
|
+
ICON_AUTH,
|
|
655
|
+
);
|
|
576
656
|
}
|
|
577
657
|
|
|
578
658
|
let apiKey = seededApiKey;
|
|
579
|
-
let validation: ApiKeyValidationResult = {
|
|
659
|
+
let validation: ApiKeyValidationResult = {
|
|
660
|
+
ok: false,
|
|
661
|
+
bots: [],
|
|
662
|
+
error: 'No validation yet.',
|
|
663
|
+
};
|
|
580
664
|
while (true) {
|
|
581
665
|
if (!apiKey) {
|
|
582
|
-
const entered = await promptRequired(
|
|
666
|
+
const entered = await promptRequired(
|
|
667
|
+
rl,
|
|
668
|
+
'HybridAI API key: ',
|
|
669
|
+
ICON_KEY,
|
|
670
|
+
);
|
|
583
671
|
apiKey = extractApiKeyFromInput(entered) || entered;
|
|
584
672
|
}
|
|
585
673
|
|
|
@@ -602,7 +690,8 @@ export async function ensureHybridAICredentials(options: OnboardingOptions = {})
|
|
|
602
690
|
apiKey = '';
|
|
603
691
|
}
|
|
604
692
|
|
|
605
|
-
const fallbackChatbotId =
|
|
693
|
+
const fallbackChatbotId =
|
|
694
|
+
getRuntimeConfig().hybridai.defaultChatbotId.trim();
|
|
606
695
|
const chosenChatbotId = await chooseDefaultBot(
|
|
607
696
|
rl,
|
|
608
697
|
validation.ok ? validation.bots : [],
|
|
@@ -619,7 +708,9 @@ export async function ensureHybridAICredentials(options: OnboardingOptions = {})
|
|
|
619
708
|
if (chosenChatbotId) {
|
|
620
709
|
printSuccess(`Default bot set to: ${chosenChatbotId}`);
|
|
621
710
|
} else {
|
|
622
|
-
printInfo(
|
|
711
|
+
printInfo(
|
|
712
|
+
'No default bot selected. You can set hybridai.defaultChatbotId in config.json later.',
|
|
713
|
+
);
|
|
623
714
|
}
|
|
624
715
|
console.log();
|
|
625
716
|
} finally {
|
package/src/proactive-policy.ts
CHANGED
|
@@ -29,7 +29,9 @@ export function isWithinActiveHours(now = new Date()): boolean {
|
|
|
29
29
|
const end = Math.max(0, Math.min(23, PROACTIVE_ACTIVE_HOURS_END));
|
|
30
30
|
if (start === end) return true;
|
|
31
31
|
|
|
32
|
-
const hour =
|
|
32
|
+
const hour =
|
|
33
|
+
resolveHourInTimezone(now, PROACTIVE_ACTIVE_HOURS_TIMEZONE) ??
|
|
34
|
+
now.getHours();
|
|
33
35
|
|
|
34
36
|
if (start < end) {
|
|
35
37
|
return hour >= start && hour < end;
|
package/src/prompt-hooks.ts
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
import { getRuntimeConfig, isSecurityTrustAccepted, SECURITY_POLICY_VERSION } from './runtime-config.js';
|
|
2
|
-
import { buildSkillsPrompt, type Skill } from './skills.js';
|
|
3
|
-
import { buildContextPrompt, loadBootstrapFiles } from './workspace.js';
|
|
4
|
-
import { APP_VERSION, HYBRIDAI_MODEL } from './config.js';
|
|
5
1
|
import fs from 'fs';
|
|
6
2
|
import os from 'os';
|
|
7
3
|
import path from 'path';
|
|
4
|
+
import { APP_VERSION, HYBRIDAI_MODEL } from './config.js';
|
|
5
|
+
import {
|
|
6
|
+
getRuntimeConfig,
|
|
7
|
+
isSecurityTrustAccepted,
|
|
8
|
+
SECURITY_POLICY_VERSION,
|
|
9
|
+
} from './runtime-config.js';
|
|
10
|
+
import { buildSkillsPrompt, type Skill } from './skills.js';
|
|
11
|
+
import { buildContextPrompt, loadBootstrapFiles } from './workspace.js';
|
|
8
12
|
|
|
9
13
|
export type PromptHookName = 'bootstrap' | 'memory' | 'safety' | 'runtime';
|
|
10
14
|
export type ExtendedPromptHookName = PromptHookName | 'proactivity';
|
|
@@ -39,12 +43,14 @@ interface PromptHook {
|
|
|
39
43
|
run: (context: PromptHookContext) => string;
|
|
40
44
|
}
|
|
41
45
|
|
|
42
|
-
export function buildSessionSummaryPrompt(
|
|
46
|
+
export function buildSessionSummaryPrompt(
|
|
47
|
+
summary: string | null | undefined,
|
|
48
|
+
): string {
|
|
43
49
|
const trimmed = summary?.trim() || '';
|
|
44
50
|
if (!trimmed) return '';
|
|
45
51
|
return [
|
|
46
52
|
'## Session Summary',
|
|
47
|
-
'Compressed context from earlier turns. Treat this as durable prior context.',
|
|
53
|
+
'Compressed and recalled context from earlier turns. Treat this as durable prior context.',
|
|
48
54
|
'',
|
|
49
55
|
trimmed,
|
|
50
56
|
].join('\n');
|
|
@@ -106,13 +112,19 @@ function buildSafetyHook(context: PromptHookContext): string {
|
|
|
106
112
|
];
|
|
107
113
|
|
|
108
114
|
if (accepted) {
|
|
109
|
-
lines.push(
|
|
115
|
+
lines.push(
|
|
116
|
+
`Trust model acceptance status: accepted (policy ${SECURITY_POLICY_VERSION}).`,
|
|
117
|
+
);
|
|
110
118
|
} else {
|
|
111
|
-
lines.push(
|
|
119
|
+
lines.push(
|
|
120
|
+
'Trust model acceptance status: missing. Remain conservative and read-only unless user intent is explicit.',
|
|
121
|
+
);
|
|
112
122
|
}
|
|
113
123
|
|
|
114
124
|
if (context.purpose === 'memory-flush') {
|
|
115
|
-
lines.push(
|
|
125
|
+
lines.push(
|
|
126
|
+
'This is a pre-compaction memory flush turn. Persist only durable memory worth keeping.',
|
|
127
|
+
);
|
|
116
128
|
}
|
|
117
129
|
|
|
118
130
|
if (context.extraSafetyText?.trim()) {
|
|
@@ -203,7 +215,9 @@ function buildProactivityHook(context: PromptHookContext): string {
|
|
|
203
215
|
}
|
|
204
216
|
|
|
205
217
|
if (context.purpose === 'memory-flush') {
|
|
206
|
-
lines.push(
|
|
218
|
+
lines.push(
|
|
219
|
+
'This is a memory-flush pass. Prioritize preserving durable context over immediate user-facing output.',
|
|
220
|
+
);
|
|
207
221
|
}
|
|
208
222
|
|
|
209
223
|
return lines.join('\n');
|
|
@@ -213,9 +227,10 @@ function buildRuntimeHook(context: PromptHookContext): string {
|
|
|
213
227
|
const runtimeInfo = context.runtimeInfo || {};
|
|
214
228
|
const model = runtimeInfo.model?.trim() || HYBRIDAI_MODEL;
|
|
215
229
|
const defaultModel = runtimeInfo.defaultModel?.trim() || HYBRIDAI_MODEL;
|
|
216
|
-
const guildLabel =
|
|
217
|
-
|
|
218
|
-
|
|
230
|
+
const guildLabel =
|
|
231
|
+
runtimeInfo.guildId === null
|
|
232
|
+
? 'dm'
|
|
233
|
+
: runtimeInfo.guildId?.trim() || 'unknown';
|
|
219
234
|
|
|
220
235
|
const lines = [
|
|
221
236
|
'## Runtime Metadata',
|
|
@@ -223,7 +238,9 @@ function buildRuntimeHook(context: PromptHookContext): string {
|
|
|
223
238
|
`Date (UTC): ${new Date().toISOString().slice(0, 10)}`,
|
|
224
239
|
`Model: ${model}`,
|
|
225
240
|
`Default model: ${defaultModel}`,
|
|
226
|
-
runtimeInfo.channelId?.trim()
|
|
241
|
+
runtimeInfo.channelId?.trim()
|
|
242
|
+
? `Channel ID: ${runtimeInfo.channelId.trim()}`
|
|
243
|
+
: '',
|
|
227
244
|
`Guild ID: ${guildLabel}`,
|
|
228
245
|
`Node: ${process.version}`,
|
|
229
246
|
`OS: ${process.platform} (${process.arch})`,
|
|
@@ -265,15 +282,21 @@ const PROMPT_HOOKS: PromptHook[] = [
|
|
|
265
282
|
];
|
|
266
283
|
|
|
267
284
|
function resolvePromptMode(context: PromptHookContext): PromptMode {
|
|
268
|
-
if (context.promptMode === 'minimal' || context.promptMode === 'none')
|
|
285
|
+
if (context.promptMode === 'minimal' || context.promptMode === 'none')
|
|
286
|
+
return context.promptMode;
|
|
269
287
|
return 'full';
|
|
270
288
|
}
|
|
271
289
|
|
|
272
|
-
function isHookAllowedForMode(
|
|
290
|
+
function isHookAllowedForMode(
|
|
291
|
+
hookName: ExtendedPromptHookName,
|
|
292
|
+
mode: PromptMode,
|
|
293
|
+
): boolean {
|
|
273
294
|
if (mode === 'none') return false;
|
|
274
295
|
if (mode === 'full') return true;
|
|
275
296
|
// Minimal mode keeps only safety + memory durability context.
|
|
276
|
-
return
|
|
297
|
+
return (
|
|
298
|
+
hookName === 'memory' || hookName === 'safety' || hookName === 'runtime'
|
|
299
|
+
);
|
|
277
300
|
}
|
|
278
301
|
|
|
279
302
|
export function runPromptHooks(context: PromptHookContext): PromptHookOutput[] {
|