@hybridaione/hybridclaw 0.1.5
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/.env.example +14 -0
- package/.github/workflows/pages.yml +41 -0
- package/AGENTS.md +24 -0
- package/CHANGELOG.md +113 -0
- package/LICENSE +21 -0
- package/README.md +226 -0
- package/SECURITY.md +67 -0
- package/config.example.json +62 -0
- package/container/Dockerfile +23 -0
- package/container/package-lock.json +246 -0
- package/container/package.json +17 -0
- package/container/src/hybridai-client.ts +38 -0
- package/container/src/index.ts +198 -0
- package/container/src/ipc.ts +37 -0
- package/container/src/tools.ts +1008 -0
- package/container/src/types.ts +74 -0
- package/container/src/web-fetch.ts +389 -0
- package/container/tsconfig.json +16 -0
- package/dist/agent.d.ts +3 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +21 -0
- package/dist/agent.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +32 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +38 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +107 -0
- package/dist/config.js.map +1 -0
- package/dist/container-runner.d.ts +11 -0
- package/dist/container-runner.d.ts.map +1 -0
- package/dist/container-runner.js +246 -0
- package/dist/container-runner.js.map +1 -0
- package/dist/conversation.d.ts +18 -0
- package/dist/conversation.d.ts.map +1 -0
- package/dist/conversation.js +29 -0
- package/dist/conversation.js.map +1 -0
- package/dist/db.d.ts +29 -0
- package/dist/db.d.ts.map +1 -0
- package/dist/db.js +205 -0
- package/dist/db.js.map +1 -0
- package/dist/discord.d.ts +17 -0
- package/dist/discord.d.ts.map +1 -0
- package/dist/discord.js +115 -0
- package/dist/discord.js.map +1 -0
- package/dist/env.d.ts +6 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +36 -0
- package/dist/env.js.map +1 -0
- package/dist/gateway-client.d.ts +8 -0
- package/dist/gateway-client.d.ts.map +1 -0
- package/dist/gateway-client.js +57 -0
- package/dist/gateway-client.js.map +1 -0
- package/dist/gateway-service.d.ts +23 -0
- package/dist/gateway-service.d.ts.map +1 -0
- package/dist/gateway-service.js +360 -0
- package/dist/gateway-service.js.map +1 -0
- package/dist/gateway-types.d.ts +40 -0
- package/dist/gateway-types.d.ts.map +1 -0
- package/dist/gateway-types.js +6 -0
- package/dist/gateway-types.js.map +1 -0
- package/dist/gateway.d.ts +2 -0
- package/dist/gateway.d.ts.map +1 -0
- package/dist/gateway.js +138 -0
- package/dist/gateway.js.map +1 -0
- package/dist/hatch.d.ts +7 -0
- package/dist/hatch.d.ts.map +1 -0
- package/dist/hatch.js +99 -0
- package/dist/hatch.js.map +1 -0
- package/dist/health.d.ts +2 -0
- package/dist/health.d.ts.map +1 -0
- package/dist/health.js +169 -0
- package/dist/health.js.map +1 -0
- package/dist/heartbeat.d.ts +3 -0
- package/dist/heartbeat.d.ts.map +1 -0
- package/dist/heartbeat.js +103 -0
- package/dist/heartbeat.js.map +1 -0
- package/dist/hybridai-bots.d.ts +5 -0
- package/dist/hybridai-bots.d.ts.map +1 -0
- package/dist/hybridai-bots.js +34 -0
- package/dist/hybridai-bots.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +60 -0
- package/dist/index.js.map +1 -0
- package/dist/ipc.d.ts +33 -0
- package/dist/ipc.d.ts.map +1 -0
- package/dist/ipc.js +142 -0
- package/dist/ipc.js.map +1 -0
- package/dist/logger.d.ts +3 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +21 -0
- package/dist/logger.js.map +1 -0
- package/dist/mount-security.d.ts +28 -0
- package/dist/mount-security.d.ts.map +1 -0
- package/dist/mount-security.js +187 -0
- package/dist/mount-security.js.map +1 -0
- package/dist/onboarding.d.ts +7 -0
- package/dist/onboarding.d.ts.map +1 -0
- package/dist/onboarding.js +445 -0
- package/dist/onboarding.js.map +1 -0
- package/dist/prompt-hooks.d.ts +17 -0
- package/dist/prompt-hooks.d.ts.map +1 -0
- package/dist/prompt-hooks.js +83 -0
- package/dist/prompt-hooks.js.map +1 -0
- package/dist/runtime-config.d.ts +78 -0
- package/dist/runtime-config.d.ts.map +1 -0
- package/dist/runtime-config.js +471 -0
- package/dist/runtime-config.js.map +1 -0
- package/dist/scheduled-task-runner.d.ts +11 -0
- package/dist/scheduled-task-runner.d.ts.map +1 -0
- package/dist/scheduled-task-runner.js +16 -0
- package/dist/scheduled-task-runner.js.map +1 -0
- package/dist/scheduler.d.ts +11 -0
- package/dist/scheduler.d.ts.map +1 -0
- package/dist/scheduler.js +165 -0
- package/dist/scheduler.js.map +1 -0
- package/dist/session-maintenance.d.ts +9 -0
- package/dist/session-maintenance.d.ts.map +1 -0
- package/dist/session-maintenance.js +168 -0
- package/dist/session-maintenance.js.map +1 -0
- package/dist/session-transcripts.d.ts +11 -0
- package/dist/session-transcripts.d.ts.map +1 -0
- package/dist/session-transcripts.js +32 -0
- package/dist/session-transcripts.js.map +1 -0
- package/dist/side-effects.d.ts +3 -0
- package/dist/side-effects.d.ts.map +1 -0
- package/dist/side-effects.js +30 -0
- package/dist/side-effects.js.map +1 -0
- package/dist/skills.d.ts +32 -0
- package/dist/skills.d.ts.map +1 -0
- package/dist/skills.js +376 -0
- package/dist/skills.js.map +1 -0
- package/dist/tui.d.ts +2 -0
- package/dist/tui.d.ts.map +1 -0
- package/dist/tui.js +305 -0
- package/dist/tui.js.map +1 -0
- package/dist/types.d.ts +132 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/workspace.d.ts +25 -0
- package/dist/workspace.d.ts.map +1 -0
- package/dist/workspace.js +154 -0
- package/dist/workspace.js.map +1 -0
- package/docs/chat.html +929 -0
- package/docs/hai_logo_free.png +0 -0
- package/docs/hero.png +0 -0
- package/docs/index.html +1213 -0
- package/package.json +34 -0
- package/skills/current-time/SKILL.md +26 -0
- package/skills/iss-position/SKILL.md +46 -0
- package/skills/iss-position/agents/openai.yaml +3 -0
- package/skills/iss-position/scripts/get_iss_position.py +107 -0
- package/skills/repo-orientation/SKILL.md +74 -0
- package/src/agent.ts +35 -0
- package/src/cli.ts +35 -0
- package/src/config.ts +137 -0
- package/src/container-runner.ts +305 -0
- package/src/conversation.ts +49 -0
- package/src/db.ts +290 -0
- package/src/discord.ts +156 -0
- package/src/env.ts +36 -0
- package/src/gateway-client.ts +73 -0
- package/src/gateway-service.ts +456 -0
- package/src/gateway-types.ts +47 -0
- package/src/gateway.ts +199 -0
- package/src/health.ts +189 -0
- package/src/heartbeat.ts +121 -0
- package/src/hybridai-bots.ts +48 -0
- package/src/ipc.ts +163 -0
- package/src/logger.ts +26 -0
- package/src/mount-security.ts +216 -0
- package/src/onboarding.ts +569 -0
- package/src/prompt-hooks.ts +113 -0
- package/src/runtime-config.ts +588 -0
- package/src/scheduled-task-runner.ts +26 -0
- package/src/scheduler.ts +196 -0
- package/src/session-maintenance.ts +263 -0
- package/src/session-transcripts.ts +45 -0
- package/src/side-effects.ts +41 -0
- package/src/skills.ts +429 -0
- package/src/tui.ts +327 -0
- package/src/types.ts +135 -0
- package/src/workspace.ts +171 -0
- package/templates/AGENTS.md +104 -0
- package/templates/BOOT.md +3 -0
- package/templates/BOOTSTRAP.md +54 -0
- package/templates/HEARTBEAT.md +5 -0
- package/templates/IDENTITY.md +18 -0
- package/templates/MEMORY.md +19 -0
- package/templates/SOUL.md +36 -0
- package/templates/TOOLS.md +22 -0
- package/templates/USER.md +17 -0
- package/tsconfig.json +18 -0
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hybridaione/hybridclaw",
|
|
3
|
+
"version": "0.1.5",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Personal AI assistant bot for Discord, powered by HybridAI",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"bin": {
|
|
10
|
+
"hybridclaw": "dist/cli.js"
|
|
11
|
+
},
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsc",
|
|
14
|
+
"lint": "tsc --noEmit --noUnusedLocals --noUnusedParameters",
|
|
15
|
+
"dev": "tsx src/cli.ts gateway",
|
|
16
|
+
"gateway": "tsx src/cli.ts gateway",
|
|
17
|
+
"tui": "tsx src/cli.ts tui",
|
|
18
|
+
"start": "node dist/cli.js gateway",
|
|
19
|
+
"build:container": "cd container && npm run build && docker build -t hybridclaw-agent ."
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"better-sqlite3": "^11.8.1",
|
|
23
|
+
"cron-parser": "^5.5.0",
|
|
24
|
+
"discord.js": "^14.16.3",
|
|
25
|
+
"pino": "^9.6.0",
|
|
26
|
+
"pino-pretty": "^13.0.0"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/better-sqlite3": "^7.6.12",
|
|
30
|
+
"@types/node": "^22.10.0",
|
|
31
|
+
"tsx": "^4.19.0",
|
|
32
|
+
"typescript": "^5.7.0"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: current-time
|
|
3
|
+
description: Return the current system time (and timezone) accurately by calling a tool, not by guessing.
|
|
4
|
+
user-invocable: true
|
|
5
|
+
disable-model-invocation: false
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Current Time
|
|
9
|
+
|
|
10
|
+
Use this skill when the user asks for the current time, date, timezone, "right now", "what time is it", or similar.
|
|
11
|
+
|
|
12
|
+
## Workflow
|
|
13
|
+
|
|
14
|
+
1. Run a real-time command with `bash`:
|
|
15
|
+
```bash
|
|
16
|
+
date +"%Y-%m-%d %H:%M:%S %Z (%z)"
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
2. Return the result in one short line:
|
|
20
|
+
`Current time skill output: <output>`
|
|
21
|
+
|
|
22
|
+
## Constraints
|
|
23
|
+
|
|
24
|
+
- Do not estimate or infer time from memory.
|
|
25
|
+
- Always use the command output from this turn.
|
|
26
|
+
- Keep the response concise unless the user asks for more detail.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: iss-position
|
|
3
|
+
description: Retrieve the current International Space Station (ISS) position from the WhereTheISS API and return standardized success/error payloads. Use when a user asks where the ISS is right now, requests current ISS latitude/longitude, or needs a reusable script to fetch live ISS coordinates.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# ISS Position
|
|
7
|
+
|
|
8
|
+
Fetch the ISS latitude and longitude with a deterministic Python script that matches this repository's existing `get_iss_position` tool behavior.
|
|
9
|
+
|
|
10
|
+
## Quick Start
|
|
11
|
+
|
|
12
|
+
1. Run:
|
|
13
|
+
```bash
|
|
14
|
+
python3 skills/iss-position/scripts/get_iss_position.py --format text
|
|
15
|
+
```
|
|
16
|
+
2. For machine-readable output, run:
|
|
17
|
+
```bash
|
|
18
|
+
python3 skills/iss-position/scripts/get_iss_position.py --format json
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Script Contract
|
|
22
|
+
|
|
23
|
+
Script path: `skills/iss-position/scripts/get_iss_position.py`
|
|
24
|
+
|
|
25
|
+
Behavior:
|
|
26
|
+
1. Call `https://api.wheretheiss.at/v1/satellites/25544`.
|
|
27
|
+
2. Use a default timeout of 15 seconds (override with `--timeout`).
|
|
28
|
+
3. Validate `latitude` and `longitude` as numeric values.
|
|
29
|
+
4. Return either:
|
|
30
|
+
```json
|
|
31
|
+
{"status":"success","latitude":12.34,"longitude":56.78,"message":"The ISS is currently at latitude 12.34 and longitude 56.78."}
|
|
32
|
+
```
|
|
33
|
+
or:
|
|
34
|
+
```json
|
|
35
|
+
{"status":"error","message":"Failed to retrieve ISS position."}
|
|
36
|
+
```
|
|
37
|
+
or:
|
|
38
|
+
```json
|
|
39
|
+
{"status":"error","message":"ISS API returned invalid latitude/longitude values"}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Notes
|
|
43
|
+
|
|
44
|
+
- Network access is required to fetch live ISS coordinates.
|
|
45
|
+
- Use `--format text` for human-readable output and `--format json` for downstream tooling.
|
|
46
|
+
- Use `--timeout` to adjust request timeout when needed.
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# ruff: noqa: INP001
|
|
2
|
+
"""Fetch the current ISS position and print a tool-compatible response."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import json
|
|
8
|
+
import sys
|
|
9
|
+
import urllib.request
|
|
10
|
+
import urllib.error
|
|
11
|
+
from typing import Any, TypedDict
|
|
12
|
+
|
|
13
|
+
API_URL = "https://api.wheretheiss.at/v1/satellites/25544"
|
|
14
|
+
DEFAULT_REQUEST_TIMEOUT = 15
|
|
15
|
+
REQUEST_FAILURE_MESSAGE = "Failed to retrieve ISS position."
|
|
16
|
+
INVALID_COORDS_MESSAGE = "ISS API returned invalid latitude/longitude values"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class IssPosition(TypedDict):
|
|
20
|
+
latitude: float
|
|
21
|
+
longitude: float
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class IssApiError(RuntimeError):
|
|
25
|
+
"""Raised when the ISS API request fails or returns invalid data."""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def fetch_current_position(timeout: int = DEFAULT_REQUEST_TIMEOUT) -> IssPosition:
|
|
29
|
+
"""Fetch current ISS coordinates from WhereTheISS."""
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
with urllib.request.urlopen(API_URL, timeout=timeout) as response:
|
|
33
|
+
payload: dict[str, Any] = json.loads(response.read())
|
|
34
|
+
except (urllib.error.URLError, OSError) as exc:
|
|
35
|
+
raise IssApiError(REQUEST_FAILURE_MESSAGE) from exc
|
|
36
|
+
|
|
37
|
+
latitude = payload.get("latitude")
|
|
38
|
+
longitude = payload.get("longitude")
|
|
39
|
+
if not isinstance(latitude, (int, float)) or not isinstance(
|
|
40
|
+
longitude, (int, float)
|
|
41
|
+
):
|
|
42
|
+
raise IssApiError(INVALID_COORDS_MESSAGE)
|
|
43
|
+
|
|
44
|
+
return IssPosition(latitude=float(latitude), longitude=float(longitude))
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _success_response(position: IssPosition) -> dict[str, Any]:
|
|
48
|
+
latitude = position["latitude"]
|
|
49
|
+
longitude = position["longitude"]
|
|
50
|
+
return {
|
|
51
|
+
"status": "success",
|
|
52
|
+
"latitude": latitude,
|
|
53
|
+
"longitude": longitude,
|
|
54
|
+
"message": f"The ISS is currently at latitude {latitude} and longitude {longitude}.",
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _error_response(message: str) -> dict[str, str]:
|
|
59
|
+
return {"status": "error", "message": message}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _parse_args() -> argparse.Namespace:
|
|
63
|
+
parser = argparse.ArgumentParser(description="Fetch the current ISS position.")
|
|
64
|
+
parser.add_argument(
|
|
65
|
+
"--format",
|
|
66
|
+
choices=("text", "json"),
|
|
67
|
+
default="json",
|
|
68
|
+
help="Output format. Use 'json' for tools and 'text' for humans.",
|
|
69
|
+
)
|
|
70
|
+
parser.add_argument(
|
|
71
|
+
"--timeout",
|
|
72
|
+
type=int,
|
|
73
|
+
default=DEFAULT_REQUEST_TIMEOUT,
|
|
74
|
+
help=f"Request timeout in seconds (default: {DEFAULT_REQUEST_TIMEOUT}).",
|
|
75
|
+
)
|
|
76
|
+
return parser.parse_args()
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _emit(payload: dict[str, Any], output_format: str) -> None:
|
|
80
|
+
if output_format == "json":
|
|
81
|
+
print(json.dumps(payload, ensure_ascii=True))
|
|
82
|
+
return
|
|
83
|
+
|
|
84
|
+
status = payload.get("status")
|
|
85
|
+
if status == "success":
|
|
86
|
+
print(payload["message"])
|
|
87
|
+
return
|
|
88
|
+
|
|
89
|
+
print(f"Error: {payload.get('message', 'Unknown error')}", file=sys.stderr)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def main() -> int:
|
|
93
|
+
args = _parse_args()
|
|
94
|
+
|
|
95
|
+
try:
|
|
96
|
+
payload = _success_response(fetch_current_position(timeout=args.timeout))
|
|
97
|
+
except IssApiError as exc:
|
|
98
|
+
payload = _error_response(str(exc))
|
|
99
|
+
_emit(payload, args.format)
|
|
100
|
+
return 1
|
|
101
|
+
|
|
102
|
+
_emit(payload, args.format)
|
|
103
|
+
return 0
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
if __name__ == "__main__":
|
|
107
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: repo-orientation
|
|
3
|
+
description: Quickly map an unfamiliar repository and identify where a requested feature should be implemented.
|
|
4
|
+
user-invocable: true
|
|
5
|
+
disable-model-invocation: false
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Repo Orientation
|
|
9
|
+
|
|
10
|
+
Use this skill when the user asks where code lives, how a feature works, or where to make a change in an unfamiliar repo.
|
|
11
|
+
|
|
12
|
+
## Tooling Notes
|
|
13
|
+
|
|
14
|
+
- Prefer `glob` + `grep` first. They are cheaper and more reliable than broad shell exploration.
|
|
15
|
+
- Use `read` sparingly for small/high-value files only.
|
|
16
|
+
- Keep the whole run under about 8 tool calls.
|
|
17
|
+
|
|
18
|
+
## Workflow
|
|
19
|
+
|
|
20
|
+
1. Establish top-level structure with `glob`.
|
|
21
|
+
Run:
|
|
22
|
+
```json
|
|
23
|
+
{"pattern":"*"}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
2. Build a focused file map with `glob`.
|
|
27
|
+
Run one or two targeted globs, not many:
|
|
28
|
+
```json
|
|
29
|
+
{"pattern":"src/**/*"}
|
|
30
|
+
{"pattern":"test/**/*"}
|
|
31
|
+
{"pattern":"docs/**/*"}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
3. Find likely implementation points by keyword with `grep`.
|
|
35
|
+
Run:
|
|
36
|
+
```json
|
|
37
|
+
{"pattern":"<keyword|feature|command|route|tool>","path":"src"}
|
|
38
|
+
```
|
|
39
|
+
If needed, repeat for one extra directory (`test` or `docs`).
|
|
40
|
+
|
|
41
|
+
4. Read only the most relevant files (max 3 reads).
|
|
42
|
+
Prioritize:
|
|
43
|
+
- entrypoints (`src/index.*`, `src/main.*`, `src/cli.*`)
|
|
44
|
+
- docs (`README.md`, `CLAUDE.md`, `AGENTS.md`)
|
|
45
|
+
- files returned by search in step 3
|
|
46
|
+
|
|
47
|
+
5. Return a concise map immediately.
|
|
48
|
+
Include:
|
|
49
|
+
- probable files to edit
|
|
50
|
+
- related files to verify
|
|
51
|
+
- risks/regressions to check
|
|
52
|
+
|
|
53
|
+
## Output Template
|
|
54
|
+
|
|
55
|
+
Use this exact structure:
|
|
56
|
+
|
|
57
|
+
```markdown
|
|
58
|
+
Feature map:
|
|
59
|
+
- Primary file(s):
|
|
60
|
+
- Supporting file(s):
|
|
61
|
+
- Tests to update:
|
|
62
|
+
|
|
63
|
+
Why these files:
|
|
64
|
+
- ...
|
|
65
|
+
|
|
66
|
+
Proposed first edit:
|
|
67
|
+
- ...
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Constraints
|
|
71
|
+
|
|
72
|
+
- Do not keep scanning once you have a plausible file map.
|
|
73
|
+
- Do not read large unrelated files "just in case."
|
|
74
|
+
- If confidence is low, state assumptions and list the next command to disambiguate.
|
package/src/agent.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent — always runs through a container for consistent sandboxing.
|
|
3
|
+
*/
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
|
|
7
|
+
import { DATA_DIR } from './config.js';
|
|
8
|
+
import { runContainer } from './container-runner.js';
|
|
9
|
+
import type { ChatMessage, ContainerOutput, ScheduledTask, ToolProgressEvent } from './types.js';
|
|
10
|
+
|
|
11
|
+
/** Write full prompt context to data/last_prompt.jsonl for debugging (Pi-Mono style). */
|
|
12
|
+
function dumpPrompt(sessionId: string, messages: ChatMessage[], model: string, chatbotId: string): void {
|
|
13
|
+
try {
|
|
14
|
+
const entry = { ts: new Date().toISOString(), sessionId, model, chatbotId, messages };
|
|
15
|
+
const filePath = path.join(DATA_DIR, 'last_prompt.jsonl');
|
|
16
|
+
fs.writeFileSync(filePath, JSON.stringify(entry) + '\n');
|
|
17
|
+
} catch { /* best-effort */ }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function runAgent(
|
|
21
|
+
sessionId: string,
|
|
22
|
+
messages: ChatMessage[],
|
|
23
|
+
chatbotId: string,
|
|
24
|
+
enableRag: boolean,
|
|
25
|
+
model: string,
|
|
26
|
+
agentId: string,
|
|
27
|
+
channelId: string,
|
|
28
|
+
scheduledTasks?: ScheduledTask[],
|
|
29
|
+
allowedTools?: string[],
|
|
30
|
+
onToolProgress?: (event: ToolProgressEvent) => void,
|
|
31
|
+
abortSignal?: AbortSignal,
|
|
32
|
+
): Promise<ContainerOutput> {
|
|
33
|
+
dumpPrompt(sessionId, messages, model, chatbotId);
|
|
34
|
+
return runContainer(sessionId, messages, chatbotId, enableRag, model, agentId, channelId, scheduledTasks, allowedTools, onToolProgress, abortSignal);
|
|
35
|
+
}
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { ensureHybridAICredentials } from './onboarding.js';
|
|
4
|
+
|
|
5
|
+
async function main(): Promise<void> {
|
|
6
|
+
const command = process.argv[2];
|
|
7
|
+
|
|
8
|
+
switch (command) {
|
|
9
|
+
case 'gateway':
|
|
10
|
+
await ensureHybridAICredentials({ commandName: 'hybridclaw gateway' });
|
|
11
|
+
await import('./gateway.js');
|
|
12
|
+
break;
|
|
13
|
+
case 'tui':
|
|
14
|
+
await ensureHybridAICredentials({ commandName: 'hybridclaw tui' });
|
|
15
|
+
await import('./tui.js');
|
|
16
|
+
break;
|
|
17
|
+
case 'onboarding':
|
|
18
|
+
await ensureHybridAICredentials({ force: true, commandName: 'hybridclaw onboarding' });
|
|
19
|
+
break;
|
|
20
|
+
default:
|
|
21
|
+
console.log(`Usage: hybridclaw <command>
|
|
22
|
+
|
|
23
|
+
Commands:
|
|
24
|
+
gateway Start core runtime (web/API/scheduler/heartbeat/optional Discord)
|
|
25
|
+
tui Start terminal adapter (requires running gateway)
|
|
26
|
+
onboarding Run HybridAI account/API key onboarding`);
|
|
27
|
+
process.exit(command ? 1 : 0);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
main().catch((err) => {
|
|
32
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
33
|
+
console.error(`hybridclaw error: ${message}`);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
});
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
import { loadEnvFile } from './env.js';
|
|
6
|
+
import {
|
|
7
|
+
ensureRuntimeConfigFile,
|
|
8
|
+
getRuntimeConfig,
|
|
9
|
+
onRuntimeConfigChange,
|
|
10
|
+
type RuntimeConfig,
|
|
11
|
+
} from './runtime-config.js';
|
|
12
|
+
|
|
13
|
+
loadEnvFile();
|
|
14
|
+
ensureRuntimeConfigFile();
|
|
15
|
+
|
|
16
|
+
function required(name: string): string {
|
|
17
|
+
const val = process.env[name];
|
|
18
|
+
if (!val) throw new Error(`Missing required env var: ${name}`);
|
|
19
|
+
return val;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function resolveAppVersion(): string {
|
|
23
|
+
const envVersion = process.env.npm_package_version;
|
|
24
|
+
if (envVersion) return envVersion;
|
|
25
|
+
|
|
26
|
+
const packagePath = path.join(process.cwd(), 'package.json');
|
|
27
|
+
try {
|
|
28
|
+
const raw = fs.readFileSync(packagePath, 'utf-8');
|
|
29
|
+
const parsed = JSON.parse(raw) as { version?: unknown };
|
|
30
|
+
if (typeof parsed.version === 'string' && parsed.version.trim()) {
|
|
31
|
+
return parsed.version.trim();
|
|
32
|
+
}
|
|
33
|
+
} catch {
|
|
34
|
+
// fall through
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return '0.0.0';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const APP_VERSION = resolveAppVersion();
|
|
41
|
+
|
|
42
|
+
// Secrets stay in env/.env
|
|
43
|
+
export const DISCORD_TOKEN = process.env.DISCORD_TOKEN || '';
|
|
44
|
+
export const HYBRIDAI_API_KEY = required('HYBRIDAI_API_KEY');
|
|
45
|
+
|
|
46
|
+
// Runtime settings hot-reload from config.json
|
|
47
|
+
export let DISCORD_PREFIX = '!claw';
|
|
48
|
+
|
|
49
|
+
export let HYBRIDAI_BASE_URL = 'https://hybridai.one';
|
|
50
|
+
export let HYBRIDAI_MODEL = 'gpt-5-nano';
|
|
51
|
+
export let HYBRIDAI_CHATBOT_ID = '';
|
|
52
|
+
export let HYBRIDAI_ENABLE_RAG = true;
|
|
53
|
+
export let HYBRIDAI_MODELS: string[] = ['gpt-5-nano', 'gpt-5-mini', 'gpt-5'];
|
|
54
|
+
|
|
55
|
+
export let CONTAINER_IMAGE = 'hybridclaw-agent';
|
|
56
|
+
export let CONTAINER_MEMORY = '512m';
|
|
57
|
+
export let CONTAINER_CPUS = '1';
|
|
58
|
+
export let CONTAINER_TIMEOUT = 60_000;
|
|
59
|
+
|
|
60
|
+
export const MOUNT_ALLOWLIST_PATH = path.join(
|
|
61
|
+
os.homedir(), '.config', 'hybridclaw', 'mount-allowlist.json',
|
|
62
|
+
);
|
|
63
|
+
export let ADDITIONAL_MOUNTS = '';
|
|
64
|
+
|
|
65
|
+
export let CONTAINER_MAX_OUTPUT_SIZE = 10_485_760;
|
|
66
|
+
export let MAX_CONCURRENT_CONTAINERS = 5;
|
|
67
|
+
|
|
68
|
+
export let HEARTBEAT_ENABLED = true;
|
|
69
|
+
export let HEARTBEAT_INTERVAL = 1_800_000;
|
|
70
|
+
export let HEARTBEAT_CHANNEL = '';
|
|
71
|
+
|
|
72
|
+
export let HEALTH_HOST = '127.0.0.1';
|
|
73
|
+
export let HEALTH_PORT = 9090;
|
|
74
|
+
export let WEB_API_TOKEN = '';
|
|
75
|
+
export let GATEWAY_BASE_URL = 'http://127.0.0.1:9090';
|
|
76
|
+
export let GATEWAY_API_TOKEN = '';
|
|
77
|
+
export let DB_PATH = 'data/hybridclaw.db';
|
|
78
|
+
export let DATA_DIR = path.dirname(DB_PATH);
|
|
79
|
+
|
|
80
|
+
export let SESSION_COMPACTION_ENABLED = true;
|
|
81
|
+
export let SESSION_COMPACTION_THRESHOLD = 120;
|
|
82
|
+
export let SESSION_COMPACTION_KEEP_RECENT = 40;
|
|
83
|
+
export let SESSION_COMPACTION_SUMMARY_MAX_CHARS = 8_000;
|
|
84
|
+
export let PRE_COMPACTION_MEMORY_FLUSH_ENABLED = true;
|
|
85
|
+
export let PRE_COMPACTION_MEMORY_FLUSH_MAX_MESSAGES = 80;
|
|
86
|
+
export let PRE_COMPACTION_MEMORY_FLUSH_MAX_CHARS = 24_000;
|
|
87
|
+
|
|
88
|
+
function applyRuntimeConfig(config: RuntimeConfig): void {
|
|
89
|
+
DISCORD_PREFIX = config.discord.prefix;
|
|
90
|
+
|
|
91
|
+
HYBRIDAI_BASE_URL = config.hybridai.baseUrl;
|
|
92
|
+
HYBRIDAI_MODEL = config.hybridai.defaultModel;
|
|
93
|
+
HYBRIDAI_CHATBOT_ID = config.hybridai.defaultChatbotId;
|
|
94
|
+
HYBRIDAI_ENABLE_RAG = config.hybridai.enableRag;
|
|
95
|
+
HYBRIDAI_MODELS = [...config.hybridai.models];
|
|
96
|
+
|
|
97
|
+
CONTAINER_IMAGE = config.container.image;
|
|
98
|
+
CONTAINER_MEMORY = config.container.memory;
|
|
99
|
+
CONTAINER_CPUS = config.container.cpus;
|
|
100
|
+
CONTAINER_TIMEOUT = config.container.timeoutMs;
|
|
101
|
+
ADDITIONAL_MOUNTS = config.container.additionalMounts;
|
|
102
|
+
CONTAINER_MAX_OUTPUT_SIZE = config.container.maxOutputBytes;
|
|
103
|
+
MAX_CONCURRENT_CONTAINERS = Math.max(1, config.container.maxConcurrent);
|
|
104
|
+
|
|
105
|
+
HEARTBEAT_ENABLED = config.heartbeat.enabled;
|
|
106
|
+
HEARTBEAT_INTERVAL = config.heartbeat.intervalMs;
|
|
107
|
+
HEARTBEAT_CHANNEL = config.heartbeat.channel;
|
|
108
|
+
|
|
109
|
+
HEALTH_HOST = config.ops.healthHost;
|
|
110
|
+
HEALTH_PORT = config.ops.healthPort;
|
|
111
|
+
WEB_API_TOKEN = process.env.WEB_API_TOKEN || config.ops.webApiToken;
|
|
112
|
+
GATEWAY_BASE_URL = config.ops.gatewayBaseUrl;
|
|
113
|
+
GATEWAY_API_TOKEN = process.env.GATEWAY_API_TOKEN || config.ops.gatewayApiToken || WEB_API_TOKEN;
|
|
114
|
+
DB_PATH = config.ops.dbPath;
|
|
115
|
+
DATA_DIR = path.dirname(DB_PATH);
|
|
116
|
+
|
|
117
|
+
SESSION_COMPACTION_ENABLED = config.sessionCompaction.enabled;
|
|
118
|
+
SESSION_COMPACTION_THRESHOLD = Math.max(20, config.sessionCompaction.threshold);
|
|
119
|
+
SESSION_COMPACTION_KEEP_RECENT = Math.max(
|
|
120
|
+
1,
|
|
121
|
+
Math.min(config.sessionCompaction.keepRecent, SESSION_COMPACTION_THRESHOLD - 1),
|
|
122
|
+
);
|
|
123
|
+
SESSION_COMPACTION_SUMMARY_MAX_CHARS = Math.max(1_000, config.sessionCompaction.summaryMaxChars);
|
|
124
|
+
PRE_COMPACTION_MEMORY_FLUSH_ENABLED = config.sessionCompaction.preCompactionMemoryFlush.enabled;
|
|
125
|
+
PRE_COMPACTION_MEMORY_FLUSH_MAX_MESSAGES = Math.max(8, config.sessionCompaction.preCompactionMemoryFlush.maxMessages);
|
|
126
|
+
PRE_COMPACTION_MEMORY_FLUSH_MAX_CHARS = Math.max(4_000, config.sessionCompaction.preCompactionMemoryFlush.maxChars);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
applyRuntimeConfig(getRuntimeConfig());
|
|
130
|
+
onRuntimeConfigChange((next) => {
|
|
131
|
+
applyRuntimeConfig(next);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
export { onRuntimeConfigChange as onConfigChange };
|
|
135
|
+
export function getConfigSnapshot(): RuntimeConfig {
|
|
136
|
+
return getRuntimeConfig();
|
|
137
|
+
}
|