hajimi-claw 0.1.0
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/Cargo.lock +2602 -0
- package/Cargo.toml +57 -0
- package/README.md +73 -0
- package/bin/hajimi-claw.js +28 -0
- package/config.example.toml +32 -0
- package/crates/hajimi-claw-agent/Cargo.toml +25 -0
- package/crates/hajimi-claw-agent/src/lib.rs +351 -0
- package/crates/hajimi-claw-bot/Cargo.toml +18 -0
- package/crates/hajimi-claw-bot/src/lib.rs +305 -0
- package/crates/hajimi-claw-daemon/Cargo.toml +24 -0
- package/crates/hajimi-claw-daemon/src/lib.rs +173 -0
- package/crates/hajimi-claw-exec/Cargo.toml +21 -0
- package/crates/hajimi-claw-exec/src/lib.rs +419 -0
- package/crates/hajimi-claw-gateway/Cargo.toml +27 -0
- package/crates/hajimi-claw-gateway/src/lib.rs +747 -0
- package/crates/hajimi-claw-llm/Cargo.toml +19 -0
- package/crates/hajimi-claw-llm/src/lib.rs +367 -0
- package/crates/hajimi-claw-policy/Cargo.toml +14 -0
- package/crates/hajimi-claw-policy/src/lib.rs +381 -0
- package/crates/hajimi-claw-store/Cargo.toml +17 -0
- package/crates/hajimi-claw-store/src/lib.rs +730 -0
- package/crates/hajimi-claw-tools/Cargo.toml +21 -0
- package/crates/hajimi-claw-tools/src/lib.rs +758 -0
- package/crates/hajimi-claw-types/Cargo.toml +16 -0
- package/crates/hajimi-claw-types/src/lib.rs +300 -0
- package/package.json +26 -0
- package/scripts/npm-install.js +45 -0
- package/src/main.rs +4 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "hajimi-claw-types"
|
|
3
|
+
version.workspace = true
|
|
4
|
+
edition.workspace = true
|
|
5
|
+
license.workspace = true
|
|
6
|
+
authors.workspace = true
|
|
7
|
+
|
|
8
|
+
[dependencies]
|
|
9
|
+
async-trait.workspace = true
|
|
10
|
+
chrono.workspace = true
|
|
11
|
+
futures.workspace = true
|
|
12
|
+
serde.workspace = true
|
|
13
|
+
serde_json.workspace = true
|
|
14
|
+
thiserror.workspace = true
|
|
15
|
+
uuid.workspace = true
|
|
16
|
+
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
use std::fmt::{Display, Formatter};
|
|
2
|
+
use std::path::PathBuf;
|
|
3
|
+
use std::pin::Pin;
|
|
4
|
+
|
|
5
|
+
use async_trait::async_trait;
|
|
6
|
+
use chrono::{DateTime, Utc};
|
|
7
|
+
use futures::Stream;
|
|
8
|
+
use serde::{Deserialize, Serialize};
|
|
9
|
+
use serde_json::Value;
|
|
10
|
+
use thiserror::Error;
|
|
11
|
+
use uuid::Uuid;
|
|
12
|
+
|
|
13
|
+
#[derive(Debug, Error)]
|
|
14
|
+
pub enum ClawError {
|
|
15
|
+
#[error("access denied: {0}")]
|
|
16
|
+
AccessDenied(String),
|
|
17
|
+
#[error("approval required: {0}")]
|
|
18
|
+
ApprovalRequired(String),
|
|
19
|
+
#[error("invalid request: {0}")]
|
|
20
|
+
InvalidRequest(String),
|
|
21
|
+
#[error("not found: {0}")]
|
|
22
|
+
NotFound(String),
|
|
23
|
+
#[error("backend error: {0}")]
|
|
24
|
+
Backend(String),
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
pub type ClawResult<T> = Result<T, ClawError>;
|
|
28
|
+
|
|
29
|
+
macro_rules! id_type {
|
|
30
|
+
($name:ident) => {
|
|
31
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
32
|
+
pub struct $name(pub Uuid);
|
|
33
|
+
|
|
34
|
+
impl $name {
|
|
35
|
+
pub fn new() -> Self {
|
|
36
|
+
Self(Uuid::new_v4())
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
impl Default for $name {
|
|
41
|
+
fn default() -> Self {
|
|
42
|
+
Self::new()
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
impl Display for $name {
|
|
47
|
+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
48
|
+
write!(f, "{}", self.0)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
id_type!(TaskId);
|
|
55
|
+
id_type!(SessionId);
|
|
56
|
+
id_type!(ApprovalId);
|
|
57
|
+
id_type!(ConversationId);
|
|
58
|
+
|
|
59
|
+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
60
|
+
pub enum ProviderKind {
|
|
61
|
+
OpenAiCompatible,
|
|
62
|
+
CustomChatCompletions,
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
impl ProviderKind {
|
|
66
|
+
pub fn as_str(&self) -> &'static str {
|
|
67
|
+
match self {
|
|
68
|
+
Self::OpenAiCompatible => "openai-compatible",
|
|
69
|
+
Self::CustomChatCompletions => "custom-chat-completions",
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
75
|
+
pub struct ProviderConfig {
|
|
76
|
+
pub id: String,
|
|
77
|
+
pub label: String,
|
|
78
|
+
pub kind: ProviderKind,
|
|
79
|
+
pub base_url: String,
|
|
80
|
+
pub api_key: String,
|
|
81
|
+
pub model: String,
|
|
82
|
+
pub enabled: bool,
|
|
83
|
+
pub extra_headers: Vec<(String, String)>,
|
|
84
|
+
pub created_at: DateTime<Utc>,
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
88
|
+
pub struct ProviderRecord {
|
|
89
|
+
pub config: ProviderConfig,
|
|
90
|
+
pub is_default: bool,
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
94
|
+
pub enum OnboardingStep {
|
|
95
|
+
ProviderLabel,
|
|
96
|
+
ProviderKind,
|
|
97
|
+
ProviderBaseUrl,
|
|
98
|
+
ProviderApiKey,
|
|
99
|
+
ProviderModel,
|
|
100
|
+
Completed,
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
|
|
104
|
+
pub struct ProviderDraft {
|
|
105
|
+
pub label: Option<String>,
|
|
106
|
+
pub kind: Option<ProviderKind>,
|
|
107
|
+
pub base_url: Option<String>,
|
|
108
|
+
pub api_key: Option<String>,
|
|
109
|
+
pub model: Option<String>,
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
113
|
+
pub struct OnboardingSession {
|
|
114
|
+
pub user_id: i64,
|
|
115
|
+
pub chat_id: i64,
|
|
116
|
+
pub step: OnboardingStep,
|
|
117
|
+
pub draft: ProviderDraft,
|
|
118
|
+
pub updated_at: DateTime<Utc>,
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
122
|
+
pub struct ProviderHealth {
|
|
123
|
+
pub ok: bool,
|
|
124
|
+
pub message: String,
|
|
125
|
+
pub suggested_models: Vec<String>,
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
|
129
|
+
pub enum PolicyMode {
|
|
130
|
+
Normal,
|
|
131
|
+
ApprovalPending,
|
|
132
|
+
ElevatedLease,
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
|
136
|
+
pub enum RiskLevel {
|
|
137
|
+
Safe,
|
|
138
|
+
Guarded,
|
|
139
|
+
Dangerous,
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
143
|
+
pub struct ExecRequest {
|
|
144
|
+
pub command: String,
|
|
145
|
+
pub args: Vec<String>,
|
|
146
|
+
pub cwd: Option<PathBuf>,
|
|
147
|
+
pub env_allowlist: Vec<String>,
|
|
148
|
+
pub timeout_secs: u64,
|
|
149
|
+
pub max_output_bytes: usize,
|
|
150
|
+
pub requires_tty: bool,
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
impl ExecRequest {
|
|
154
|
+
pub fn summary(&self) -> String {
|
|
155
|
+
if self.args.is_empty() {
|
|
156
|
+
self.command.clone()
|
|
157
|
+
} else {
|
|
158
|
+
format!("{} {}", self.command, self.args.join(" "))
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
164
|
+
pub struct ExecResult {
|
|
165
|
+
pub exit_code: Option<i32>,
|
|
166
|
+
pub stdout: String,
|
|
167
|
+
pub stderr: String,
|
|
168
|
+
pub duration_ms: u128,
|
|
169
|
+
pub truncated: bool,
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
173
|
+
pub struct SessionOpenRequest {
|
|
174
|
+
pub name: Option<String>,
|
|
175
|
+
pub cwd: Option<PathBuf>,
|
|
176
|
+
pub env_allowlist: Vec<String>,
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
180
|
+
pub struct SessionHandle {
|
|
181
|
+
pub id: SessionId,
|
|
182
|
+
pub name: String,
|
|
183
|
+
pub cwd: PathBuf,
|
|
184
|
+
pub created_at: DateTime<Utc>,
|
|
185
|
+
pub last_used_at: DateTime<Utc>,
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
189
|
+
pub struct SessionSummary {
|
|
190
|
+
pub session_id: SessionId,
|
|
191
|
+
pub cwd: PathBuf,
|
|
192
|
+
pub recent_commands: Vec<String>,
|
|
193
|
+
pub summary: String,
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
197
|
+
pub struct ApprovalRequest {
|
|
198
|
+
pub request_id: ApprovalId,
|
|
199
|
+
pub reason: String,
|
|
200
|
+
pub risk_level: RiskLevel,
|
|
201
|
+
pub command_preview: String,
|
|
202
|
+
pub cwd: Option<PathBuf>,
|
|
203
|
+
pub expires_at: DateTime<Utc>,
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
207
|
+
pub struct ApprovalDecision {
|
|
208
|
+
pub request_id: ApprovalId,
|
|
209
|
+
pub approved: bool,
|
|
210
|
+
pub decided_at: DateTime<Utc>,
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
214
|
+
pub struct ToolSpec {
|
|
215
|
+
pub name: String,
|
|
216
|
+
pub description: String,
|
|
217
|
+
pub requires_approval: bool,
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
221
|
+
pub struct ToolOutput {
|
|
222
|
+
pub content: String,
|
|
223
|
+
pub structured: Option<Value>,
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
227
|
+
pub struct ToolContext {
|
|
228
|
+
pub conversation_id: ConversationId,
|
|
229
|
+
pub working_directory: Option<PathBuf>,
|
|
230
|
+
pub elevated: bool,
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
234
|
+
pub enum MessageRole {
|
|
235
|
+
System,
|
|
236
|
+
User,
|
|
237
|
+
Assistant,
|
|
238
|
+
Tool,
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
242
|
+
pub struct ConversationMessage {
|
|
243
|
+
pub role: MessageRole,
|
|
244
|
+
pub content: String,
|
|
245
|
+
pub created_at: DateTime<Utc>,
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
249
|
+
pub struct AgentRequest {
|
|
250
|
+
pub conversation_id: ConversationId,
|
|
251
|
+
pub provider_id: Option<String>,
|
|
252
|
+
pub system_prompt: String,
|
|
253
|
+
pub messages: Vec<ConversationMessage>,
|
|
254
|
+
pub tool_specs: Vec<ToolSpec>,
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
258
|
+
pub enum AgentEvent {
|
|
259
|
+
TextDelta(String),
|
|
260
|
+
ToolCall { tool: String, input: Value },
|
|
261
|
+
Finished,
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
pub type AgentStream = Pin<Box<dyn Stream<Item = ClawResult<AgentEvent>> + Send>>;
|
|
265
|
+
|
|
266
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
267
|
+
pub enum TaskKind {
|
|
268
|
+
EphemeralAgentTask,
|
|
269
|
+
PersistentShellTask,
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
273
|
+
pub struct TaskStatus {
|
|
274
|
+
pub id: TaskId,
|
|
275
|
+
pub kind: TaskKind,
|
|
276
|
+
pub description: String,
|
|
277
|
+
pub queued_at: DateTime<Utc>,
|
|
278
|
+
pub started_at: Option<DateTime<Utc>>,
|
|
279
|
+
pub finished_at: Option<DateTime<Utc>>,
|
|
280
|
+
pub running: bool,
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
#[async_trait]
|
|
284
|
+
pub trait LlmBackend: Send + Sync {
|
|
285
|
+
async fn respond(&self, req: AgentRequest) -> ClawResult<AgentStream>;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
#[async_trait]
|
|
289
|
+
pub trait Tool: Send + Sync {
|
|
290
|
+
fn spec(&self) -> ToolSpec;
|
|
291
|
+
async fn call(&self, ctx: ToolContext, input: Value) -> ClawResult<ToolOutput>;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
#[async_trait]
|
|
295
|
+
pub trait Executor: Send + Sync {
|
|
296
|
+
async fn run_once(&self, req: ExecRequest) -> ClawResult<ExecResult>;
|
|
297
|
+
async fn open_session(&self, req: SessionOpenRequest) -> ClawResult<SessionHandle>;
|
|
298
|
+
async fn run_in_session(&self, id: SessionId, req: ExecRequest) -> ClawResult<ExecResult>;
|
|
299
|
+
async fn close_session(&self, id: SessionId) -> ClawResult<()>;
|
|
300
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "hajimi-claw",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Single-user Telegram-first ops agent in Rust",
|
|
5
|
+
"bin": {
|
|
6
|
+
"hajimi-claw": "bin/hajimi-claw.js"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"Cargo.toml",
|
|
10
|
+
"Cargo.lock",
|
|
11
|
+
"src",
|
|
12
|
+
"crates",
|
|
13
|
+
"bin",
|
|
14
|
+
"scripts/npm-install.js",
|
|
15
|
+
"config.example.toml",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"postinstall": "node scripts/npm-install.js"
|
|
20
|
+
},
|
|
21
|
+
"preferGlobal": true,
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=18"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const { spawnSync } = require("child_process");
|
|
6
|
+
|
|
7
|
+
const root = path.resolve(__dirname, "..");
|
|
8
|
+
const cargo = process.platform === "win32" ? "cargo.exe" : "cargo";
|
|
9
|
+
const binaryName = process.platform === "win32" ? "hajimi-claw.exe" : "hajimi-claw";
|
|
10
|
+
const sourceBinary = path.join(root, "target", "release", binaryName);
|
|
11
|
+
const outputDir = path.join(root, "npm-dist");
|
|
12
|
+
const outputBinary = path.join(outputDir, binaryName);
|
|
13
|
+
|
|
14
|
+
function fail(message) {
|
|
15
|
+
console.error(message);
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const cargoCheck = spawnSync(cargo, ["--version"], { cwd: root, stdio: "pipe" });
|
|
20
|
+
if (cargoCheck.error || cargoCheck.status !== 0) {
|
|
21
|
+
fail("cargo is required to install the npm package for hajimi-claw.");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const build = spawnSync(
|
|
25
|
+
cargo,
|
|
26
|
+
["build", "--release", "--manifest-path", path.join(root, "Cargo.toml")],
|
|
27
|
+
{
|
|
28
|
+
cwd: root,
|
|
29
|
+
stdio: "inherit",
|
|
30
|
+
}
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
if (build.status !== 0) {
|
|
34
|
+
fail("cargo build --release failed during npm install.");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!fs.existsSync(sourceBinary)) {
|
|
38
|
+
fail(`release binary not found at ${sourceBinary}`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
42
|
+
fs.copyFileSync(sourceBinary, outputBinary);
|
|
43
|
+
if (process.platform !== "win32") {
|
|
44
|
+
fs.chmodSync(outputBinary, 0o755);
|
|
45
|
+
}
|
package/src/main.rs
ADDED