@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/cli.ts
CHANGED
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
import { spawn, spawnSync } from 'child_process';
|
|
3
4
|
import fs from 'fs';
|
|
4
5
|
import path from 'path';
|
|
5
|
-
import { spawn, spawnSync } from 'child_process';
|
|
6
6
|
import readline from 'readline/promises';
|
|
7
|
-
|
|
7
|
+
import {
|
|
8
|
+
APP_VERSION,
|
|
9
|
+
DATA_DIR,
|
|
10
|
+
GATEWAY_BASE_URL,
|
|
11
|
+
MissingRequiredEnvVarError,
|
|
12
|
+
} from './config.js';
|
|
8
13
|
import { ensureHybridAICredentials } from './onboarding.js';
|
|
9
|
-
import { APP_VERSION, DATA_DIR, GATEWAY_BASE_URL, MissingRequiredEnvVarError } from './config.js';
|
|
10
14
|
import { printUpdateUsage, runUpdateCommand } from './update.js';
|
|
11
15
|
|
|
12
|
-
async function ensureRuntimeContainer(
|
|
16
|
+
async function ensureRuntimeContainer(
|
|
17
|
+
commandName: string,
|
|
18
|
+
required = true,
|
|
19
|
+
): Promise<void> {
|
|
13
20
|
const { ensureContainerImageReady } = await import('./container-setup.js');
|
|
14
21
|
await ensureContainerImageReady({
|
|
15
22
|
commandName,
|
|
@@ -43,13 +50,15 @@ async function ensureGatewayForTui(commandName: string): Promise<void> {
|
|
|
43
50
|
return;
|
|
44
51
|
}
|
|
45
52
|
|
|
46
|
-
console.log(
|
|
53
|
+
console.log(
|
|
54
|
+
`${commandName}: Gateway not found. Starting gateway backend at ${GATEWAY_BASE_URL}.`,
|
|
55
|
+
);
|
|
47
56
|
await startGatewayBackend(commandName, true);
|
|
48
57
|
|
|
49
58
|
if (!(await isGatewayReachable())) {
|
|
50
59
|
throw new Error(
|
|
51
|
-
`Gateway did not become available at ${GATEWAY_BASE_URL} after startup.`
|
|
52
|
-
|
|
60
|
+
`Gateway did not become available at ${GATEWAY_BASE_URL} after startup.` +
|
|
61
|
+
' Please run `hybridclaw gateway start --foreground` in another terminal and try again.',
|
|
53
62
|
);
|
|
54
63
|
}
|
|
55
64
|
}
|
|
@@ -81,17 +90,17 @@ function formatInstructionDiffLine(file: {
|
|
|
81
90
|
];
|
|
82
91
|
}
|
|
83
92
|
|
|
84
|
-
async function ensureTuiInstructionApproval(
|
|
93
|
+
async function ensureTuiInstructionApproval(
|
|
94
|
+
commandName: string,
|
|
95
|
+
): Promise<void> {
|
|
85
96
|
const {
|
|
86
97
|
approveInstructionBaseline,
|
|
87
98
|
INSTRUCTION_BASELINE_PATH,
|
|
88
99
|
summarizeInstructionIntegrity,
|
|
89
100
|
verifyInstructionBaseline,
|
|
90
101
|
} = await import('./instruction-integrity.js');
|
|
91
|
-
const {
|
|
92
|
-
|
|
93
|
-
completeInstructionApprovalAudit,
|
|
94
|
-
} = await import('./instruction-approval-audit.js');
|
|
102
|
+
const { beginInstructionApprovalAudit, completeInstructionApprovalAudit } =
|
|
103
|
+
await import('./instruction-approval-audit.js');
|
|
95
104
|
|
|
96
105
|
const result = verifyInstructionBaseline();
|
|
97
106
|
if (result.ok) return;
|
|
@@ -107,7 +116,9 @@ async function ensureTuiInstructionApproval(commandName: string): Promise<void>
|
|
|
107
116
|
console.error(`Instruction baseline is invalid: ${result.baselineError}`);
|
|
108
117
|
console.error(`Baseline path: ${INSTRUCTION_BASELINE_PATH}`);
|
|
109
118
|
} else if (!result.baseline) {
|
|
110
|
-
console.error(
|
|
119
|
+
console.error(
|
|
120
|
+
`No approved instruction baseline found at ${INSTRUCTION_BASELINE_PATH}.`,
|
|
121
|
+
);
|
|
111
122
|
}
|
|
112
123
|
|
|
113
124
|
const changed = result.files.filter((file) => file.status !== 'ok');
|
|
@@ -133,10 +144,17 @@ async function ensureTuiInstructionApproval(commandName: string): Promise<void>
|
|
|
133
144
|
);
|
|
134
145
|
}
|
|
135
146
|
|
|
136
|
-
const rl = readline.createInterface({
|
|
147
|
+
const rl = readline.createInterface({
|
|
148
|
+
input: process.stdin,
|
|
149
|
+
output: process.stdout,
|
|
150
|
+
});
|
|
137
151
|
let answer = '';
|
|
138
152
|
try {
|
|
139
|
-
answer = (
|
|
153
|
+
answer = (
|
|
154
|
+
await rl.question('Approve current instruction changes now? [y/N] ')
|
|
155
|
+
)
|
|
156
|
+
.trim()
|
|
157
|
+
.toLowerCase();
|
|
140
158
|
} finally {
|
|
141
159
|
rl.close();
|
|
142
160
|
}
|
|
@@ -156,7 +174,9 @@ async function ensureTuiInstructionApproval(commandName: string): Promise<void>
|
|
|
156
174
|
|
|
157
175
|
try {
|
|
158
176
|
const baseline = approveInstructionBaseline();
|
|
159
|
-
console.log(
|
|
177
|
+
console.log(
|
|
178
|
+
`Approved instruction baseline at ${INSTRUCTION_BASELINE_PATH} (${baseline.approvedAt}).`,
|
|
179
|
+
);
|
|
160
180
|
completeInstructionApprovalAudit({
|
|
161
181
|
context: auditContext,
|
|
162
182
|
approved: true,
|
|
@@ -186,7 +206,10 @@ function printMainUsage(): void {
|
|
|
186
206
|
onboarding Run HybridAI account/API key onboarding
|
|
187
207
|
update Check and apply HybridClaw CLI updates
|
|
188
208
|
audit Inspect/verify structured audit trail
|
|
189
|
-
help Show general or topic-specific help (e.g. \`hybridclaw help gateway\`)
|
|
209
|
+
help Show general or topic-specific help (e.g. \`hybridclaw help gateway\`)
|
|
210
|
+
|
|
211
|
+
Options:
|
|
212
|
+
--version, -v Show HybridClaw CLI version`);
|
|
190
213
|
}
|
|
191
214
|
|
|
192
215
|
function printGatewayUsage(): void {
|
|
@@ -309,12 +332,19 @@ function readGatewayPid(): GatewayPidState | null {
|
|
|
309
332
|
try {
|
|
310
333
|
const raw = fs.readFileSync(GATEWAY_PID_PATH, 'utf-8');
|
|
311
334
|
const parsed = JSON.parse(raw) as Partial<GatewayPidState>;
|
|
312
|
-
if (
|
|
335
|
+
if (
|
|
336
|
+
!parsed ||
|
|
337
|
+
typeof parsed.pid !== 'number' ||
|
|
338
|
+
!Number.isFinite(parsed.pid)
|
|
339
|
+
)
|
|
340
|
+
return null;
|
|
313
341
|
return {
|
|
314
342
|
pid: parsed.pid,
|
|
315
343
|
startedAt: typeof parsed.startedAt === 'string' ? parsed.startedAt : '',
|
|
316
344
|
cwd: typeof parsed.cwd === 'string' ? parsed.cwd : '',
|
|
317
|
-
command: Array.isArray(parsed.command)
|
|
345
|
+
command: Array.isArray(parsed.command)
|
|
346
|
+
? parsed.command.map((item) => String(item))
|
|
347
|
+
: [],
|
|
318
348
|
};
|
|
319
349
|
} catch {
|
|
320
350
|
return null;
|
|
@@ -364,7 +394,11 @@ function parseGatewayBaseUrl(): URL | null {
|
|
|
364
394
|
|
|
365
395
|
function isLocalGatewayHost(hostname: string): boolean {
|
|
366
396
|
const normalized = hostname.trim().toLowerCase();
|
|
367
|
-
return
|
|
397
|
+
return (
|
|
398
|
+
normalized === '127.0.0.1' ||
|
|
399
|
+
normalized === 'localhost' ||
|
|
400
|
+
normalized === '::1'
|
|
401
|
+
);
|
|
368
402
|
}
|
|
369
403
|
|
|
370
404
|
function resolveGatewayListenPort(url: URL): number {
|
|
@@ -380,9 +414,13 @@ function findGatewayPidByPort(): number | null {
|
|
|
380
414
|
if (!parsed || !isLocalGatewayHost(parsed.hostname)) return null;
|
|
381
415
|
const port = resolveGatewayListenPort(parsed);
|
|
382
416
|
|
|
383
|
-
const result = spawnSync(
|
|
384
|
-
|
|
385
|
-
|
|
417
|
+
const result = spawnSync(
|
|
418
|
+
'lsof',
|
|
419
|
+
['-nP', `-iTCP:${port}`, '-sTCP:LISTEN', '-t'],
|
|
420
|
+
{
|
|
421
|
+
encoding: 'utf-8',
|
|
422
|
+
},
|
|
423
|
+
);
|
|
386
424
|
if (result.error) return null;
|
|
387
425
|
const output = (result.stdout || '').trim();
|
|
388
426
|
if (!output) return null;
|
|
@@ -408,12 +446,14 @@ function adoptGatewayPid(pid: number, source: string): boolean {
|
|
|
408
446
|
async function adoptReachableGatewayIfPossible(): Promise<boolean> {
|
|
409
447
|
const { gatewayStatus } = await import('./gateway-client.js');
|
|
410
448
|
const status = await gatewayStatus();
|
|
411
|
-
const pid =
|
|
412
|
-
|
|
413
|
-
|
|
449
|
+
const pid =
|
|
450
|
+
typeof status.pid === 'number' && Number.isFinite(status.pid)
|
|
451
|
+
? Math.floor(status.pid)
|
|
452
|
+
: 0;
|
|
414
453
|
if (pid > 0 && adoptGatewayPid(pid, 'api-status')) return true;
|
|
415
454
|
const fallbackPid = findGatewayPidByPort();
|
|
416
|
-
if (fallbackPid && adoptGatewayPid(fallbackPid, 'lsof-port-probe'))
|
|
455
|
+
if (fallbackPid && adoptGatewayPid(fallbackPid, 'lsof-port-probe'))
|
|
456
|
+
return true;
|
|
417
457
|
return false;
|
|
418
458
|
}
|
|
419
459
|
|
|
@@ -423,11 +463,16 @@ async function runGatewayForeground(commandName: string): Promise<void> {
|
|
|
423
463
|
await import('./gateway.js');
|
|
424
464
|
}
|
|
425
465
|
|
|
426
|
-
async function startGatewayBackend(
|
|
466
|
+
async function startGatewayBackend(
|
|
467
|
+
commandName: string,
|
|
468
|
+
waitForHealthy = false,
|
|
469
|
+
): Promise<void> {
|
|
427
470
|
if (await isGatewayReachable()) {
|
|
428
471
|
const existing = readGatewayPid();
|
|
429
472
|
if (existing && isPidRunning(existing.pid)) {
|
|
430
|
-
console.log(
|
|
473
|
+
console.log(
|
|
474
|
+
`Gateway already running in backend mode (pid ${existing.pid}).`,
|
|
475
|
+
);
|
|
431
476
|
} else {
|
|
432
477
|
let adopted = false;
|
|
433
478
|
try {
|
|
@@ -437,9 +482,13 @@ async function startGatewayBackend(commandName: string, waitForHealthy = false):
|
|
|
437
482
|
}
|
|
438
483
|
if (adopted) {
|
|
439
484
|
const adoptedState = readGatewayPid();
|
|
440
|
-
console.log(
|
|
485
|
+
console.log(
|
|
486
|
+
`Gateway already reachable at ${GATEWAY_BASE_URL}; adopted pid ${adoptedState?.pid || '(unknown)'}.`,
|
|
487
|
+
);
|
|
441
488
|
} else {
|
|
442
|
-
console.log(
|
|
489
|
+
console.log(
|
|
490
|
+
`Gateway already reachable at ${GATEWAY_BASE_URL} (unmanaged by CLI PID file).`,
|
|
491
|
+
);
|
|
443
492
|
}
|
|
444
493
|
}
|
|
445
494
|
return;
|
|
@@ -451,12 +500,14 @@ async function startGatewayBackend(commandName: string, waitForHealthy = false):
|
|
|
451
500
|
const healthy = await waitForGatewayReachable(15_000);
|
|
452
501
|
if (!healthy) {
|
|
453
502
|
throw new Error(
|
|
454
|
-
`Gateway process ${existing.pid} exists but did not become reachable at ${GATEWAY_BASE_URL}.`
|
|
455
|
-
|
|
503
|
+
`Gateway process ${existing.pid} exists but did not become reachable at ${GATEWAY_BASE_URL}.` +
|
|
504
|
+
` Check logs: ${GATEWAY_LOG_PATH}`,
|
|
456
505
|
);
|
|
457
506
|
}
|
|
458
507
|
}
|
|
459
|
-
console.log(
|
|
508
|
+
console.log(
|
|
509
|
+
`Gateway already running in backend mode (pid ${existing.pid}).`,
|
|
510
|
+
);
|
|
460
511
|
return;
|
|
461
512
|
}
|
|
462
513
|
if (existing && !isPidRunning(existing.pid)) {
|
|
@@ -494,8 +545,8 @@ async function startGatewayBackend(commandName: string, waitForHealthy = false):
|
|
|
494
545
|
const healthy = await waitForGatewayReachable(20_000);
|
|
495
546
|
if (!healthy) {
|
|
496
547
|
throw new Error(
|
|
497
|
-
`Gateway backend started (pid ${child.pid}) but not reachable at ${GATEWAY_BASE_URL}.`
|
|
498
|
-
|
|
548
|
+
`Gateway backend started (pid ${child.pid}) but not reachable at ${GATEWAY_BASE_URL}.` +
|
|
549
|
+
` Check logs: ${GATEWAY_LOG_PATH}`,
|
|
499
550
|
);
|
|
500
551
|
}
|
|
501
552
|
}
|
|
@@ -514,7 +565,9 @@ async function stopManagedGatewayByPid(state: GatewayPidState): Promise<void> {
|
|
|
514
565
|
process.kill(state.pid, 'SIGTERM');
|
|
515
566
|
} catch (err) {
|
|
516
567
|
removeGatewayPidFile();
|
|
517
|
-
throw new Error(
|
|
568
|
+
throw new Error(
|
|
569
|
+
`Failed to stop gateway pid ${state.pid}: ${err instanceof Error ? err.message : String(err)}`,
|
|
570
|
+
);
|
|
518
571
|
}
|
|
519
572
|
|
|
520
573
|
const deadline = Date.now() + 10_000;
|
|
@@ -538,7 +591,9 @@ async function stopManagedGatewayByPid(state: GatewayPidState): Promise<void> {
|
|
|
538
591
|
console.log(`Gateway stop timed out; sent SIGKILL to pid ${state.pid}.`);
|
|
539
592
|
}
|
|
540
593
|
|
|
541
|
-
async function stopUnmanagedGatewayGracefully(
|
|
594
|
+
async function stopUnmanagedGatewayGracefully(
|
|
595
|
+
mode: 'stop' | 'restart',
|
|
596
|
+
): Promise<void> {
|
|
542
597
|
const suffix = mode === 'restart' ? ' before restart' : '';
|
|
543
598
|
console.log('Gateway is reachable but unmanaged by CLI PID file.');
|
|
544
599
|
console.log(`Requesting graceful shutdown over API${suffix}...`);
|
|
@@ -548,8 +603,8 @@ async function stopUnmanagedGatewayGracefully(mode: 'stop' | 'restart'): Promise
|
|
|
548
603
|
const stopped = await waitForGatewayUnreachable(10_000);
|
|
549
604
|
if (!stopped) {
|
|
550
605
|
throw new Error(
|
|
551
|
-
`Gateway remained reachable at ${GATEWAY_BASE_URL} after shutdown request.`
|
|
552
|
-
|
|
606
|
+
`Gateway remained reachable at ${GATEWAY_BASE_URL} after shutdown request.` +
|
|
607
|
+
' Stop it from its owning process or use your system process manager.',
|
|
553
608
|
);
|
|
554
609
|
}
|
|
555
610
|
console.log('Unmanaged gateway stopped via API request.');
|
|
@@ -559,18 +614,24 @@ async function stopUnmanagedGatewayGracefully(mode: 'stop' | 'restart'): Promise
|
|
|
559
614
|
}
|
|
560
615
|
|
|
561
616
|
const discoveredPid = findGatewayPidByPort();
|
|
562
|
-
if (
|
|
617
|
+
if (
|
|
618
|
+
discoveredPid &&
|
|
619
|
+
discoveredPid !== process.pid &&
|
|
620
|
+
adoptGatewayPid(discoveredPid, 'lsof-port-probe')
|
|
621
|
+
) {
|
|
563
622
|
const adoptedState = readGatewayPid();
|
|
564
623
|
if (adoptedState && isPidRunning(adoptedState.pid)) {
|
|
565
|
-
console.log(
|
|
624
|
+
console.log(
|
|
625
|
+
`Shutdown API unavailable; stopping gateway pid ${adoptedState.pid} via local signal.`,
|
|
626
|
+
);
|
|
566
627
|
await stopManagedGatewayByPid(adoptedState);
|
|
567
628
|
return;
|
|
568
629
|
}
|
|
569
630
|
}
|
|
570
631
|
|
|
571
632
|
throw new Error(
|
|
572
|
-
`Gateway shutdown endpoint is unavailable at ${GATEWAY_BASE_URL} and PID ownership could not be recovered.`
|
|
573
|
-
|
|
633
|
+
`Gateway shutdown endpoint is unavailable at ${GATEWAY_BASE_URL} and PID ownership could not be recovered.` +
|
|
634
|
+
' Stop the process manually once, then retry.',
|
|
574
635
|
);
|
|
575
636
|
}
|
|
576
637
|
|
|
@@ -588,11 +649,15 @@ async function stopGatewayBackend(): Promise<void> {
|
|
|
588
649
|
if (!isPidRunning(state.pid)) {
|
|
589
650
|
removeGatewayPidFile();
|
|
590
651
|
if (await isGatewayReachable()) {
|
|
591
|
-
console.log(
|
|
652
|
+
console.log(
|
|
653
|
+
`Removed stale gateway PID file (pid ${state.pid} not running).`,
|
|
654
|
+
);
|
|
592
655
|
await stopUnmanagedGatewayGracefully('stop');
|
|
593
656
|
return;
|
|
594
657
|
}
|
|
595
|
-
console.log(
|
|
658
|
+
console.log(
|
|
659
|
+
`Removed stale gateway PID file (pid ${state.pid} not running).`,
|
|
660
|
+
);
|
|
596
661
|
return;
|
|
597
662
|
}
|
|
598
663
|
|
|
@@ -611,21 +676,29 @@ async function printGatewayLifecycleStatus(): Promise<void> {
|
|
|
611
676
|
} else {
|
|
612
677
|
console.log('PID file: not found');
|
|
613
678
|
}
|
|
614
|
-
console.log(
|
|
679
|
+
console.log(
|
|
680
|
+
`Gateway API reachable: ${reachable ? 'yes' : 'no'} (${GATEWAY_BASE_URL})`,
|
|
681
|
+
);
|
|
615
682
|
|
|
616
683
|
if (reachable) {
|
|
617
684
|
try {
|
|
618
685
|
const { gatewayStatus } = await import('./gateway-client.js');
|
|
619
686
|
const status = await gatewayStatus();
|
|
620
|
-
console.log(
|
|
687
|
+
console.log(
|
|
688
|
+
`Uptime: ${status.uptime}s | Sessions: ${status.sessions} | Containers: ${status.activeContainers}`,
|
|
689
|
+
);
|
|
621
690
|
} catch (err) {
|
|
622
|
-
console.log(
|
|
691
|
+
console.log(
|
|
692
|
+
`Gateway status fetch failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
693
|
+
);
|
|
623
694
|
}
|
|
624
695
|
}
|
|
625
696
|
}
|
|
626
697
|
|
|
627
698
|
async function runGatewayApiCommand(args: string[]): Promise<void> {
|
|
628
|
-
const { gatewayCommand, renderGatewayCommand } = await import(
|
|
699
|
+
const { gatewayCommand, renderGatewayCommand } = await import(
|
|
700
|
+
'./gateway-client.js'
|
|
701
|
+
);
|
|
629
702
|
const result = await gatewayCommand({
|
|
630
703
|
sessionId: 'cli:gateway',
|
|
631
704
|
guildId: null,
|
|
@@ -703,6 +776,10 @@ async function main(): Promise<void> {
|
|
|
703
776
|
const subargs = process.argv.slice(3);
|
|
704
777
|
|
|
705
778
|
switch (command) {
|
|
779
|
+
case '--version':
|
|
780
|
+
case '-v':
|
|
781
|
+
console.log(APP_VERSION);
|
|
782
|
+
break;
|
|
706
783
|
case 'gateway':
|
|
707
784
|
await handleGatewayCommand(subargs);
|
|
708
785
|
break;
|
|
@@ -722,7 +799,10 @@ async function main(): Promise<void> {
|
|
|
722
799
|
printOnboardingUsage();
|
|
723
800
|
break;
|
|
724
801
|
}
|
|
725
|
-
await ensureHybridAICredentials({
|
|
802
|
+
await ensureHybridAICredentials({
|
|
803
|
+
force: true,
|
|
804
|
+
commandName: 'hybridclaw onboarding',
|
|
805
|
+
});
|
|
726
806
|
await ensureRuntimeContainer('hybridclaw onboarding', false);
|
|
727
807
|
break;
|
|
728
808
|
case 'update': {
|
|
@@ -772,16 +852,21 @@ const envVarHint: Record<string, string> = {
|
|
|
772
852
|
};
|
|
773
853
|
|
|
774
854
|
function printMissingEnvVarError(message: string, envVar?: string): void {
|
|
775
|
-
const hint = envVar
|
|
855
|
+
const hint = envVar
|
|
856
|
+
? envVarHint[envVar]
|
|
857
|
+
: 'Set this variable and rerun the command.';
|
|
776
858
|
console.error(`hybridclaw error: ${message}`);
|
|
777
859
|
console.error(`Hint: ${hint}`);
|
|
778
|
-
console.error(
|
|
860
|
+
console.error(
|
|
861
|
+
'Make sure you run `hybridclaw` from the directory that contains your .env file.',
|
|
862
|
+
);
|
|
779
863
|
}
|
|
780
864
|
|
|
781
865
|
main().catch((err) => {
|
|
782
|
-
const missingEnvVarMatch =
|
|
783
|
-
|
|
784
|
-
|
|
866
|
+
const missingEnvVarMatch =
|
|
867
|
+
err instanceof Error
|
|
868
|
+
? err.message.match(/^Missing required env var:\s*([A-Za-z0-9_]+)/)
|
|
869
|
+
: null;
|
|
785
870
|
if (missingEnvVarMatch) {
|
|
786
871
|
printMissingEnvVarError(err.message, missingEnvVarMatch[1]);
|
|
787
872
|
} else if (err instanceof MissingRequiredEnvVarError) {
|