@jx0/agency 0.2.1 → 0.4.1
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/README.md +120 -52
- package/dashboard/out/404.html +1 -1
- package/dashboard/out/_next/static/chunks/app/_not-found/{page-ad40673d821037f6.js → page-5cb94002960ab71a.js} +1 -1
- package/dashboard/out/_next/static/chunks/app/layout-6249f74085ad56b1.js +1 -0
- package/dashboard/out/_next/static/chunks/app/page-0a5ee03ddf4553ab.js +1 -0
- package/dashboard/out/_next/static/chunks/{main-app-1d848b791b823fa6.js → main-app-0398d52862f5c730.js} +1 -1
- package/dashboard/out/_next/static/css/a13af72b10a7d74f.css +1 -0
- package/dashboard/out/index.html +1 -1
- package/dashboard/out/index.txt +4 -4
- package/docs/images/agency_cli_ps.png +0 -0
- package/docs/images/agency_ui_ai_prodivder_settings.png +0 -0
- package/docs/images/agency_ui_aws_settings.png +0 -0
- package/docs/images/agency_ui_identity_settings.png +0 -0
- package/docs/images/agency_ui_import_skills.jpeg +0 -0
- package/docs/images/agency_ui_knowledge.png +0 -0
- package/docs/images/agency_ui_mission_control.png +0 -0
- package/docs/images/agency_ui_skills_marketplace.png +0 -0
- package/docs/images/agent_ui_agent_config.png +0 -0
- package/package.json +1 -1
- package/src/api/db/migrations/004_nullable_human_refs.ts +129 -0
- package/src/api/db/migrations/005_agent_skills.ts +14 -0
- package/src/api/db/migrations/006_runtime_machine.ts +24 -0
- package/src/api/db/seed.ts +62 -46
- package/src/api/index.ts +11 -4
- package/src/api/lib/deploy.ts +412 -0
- package/src/api/lib/env-vars.ts +19 -0
- package/src/api/lib/exec.ts +77 -0
- package/src/api/lib/fleet-sync.ts +49 -32
- package/src/api/lib/fs-store.ts +350 -0
- package/src/api/lib/import-skills.ts +105 -0
- package/src/api/lib/metrics.ts +183 -0
- package/src/api/lib/processes.ts +82 -12
- package/src/api/lib/provision-openclaw.ts +407 -0
- package/src/api/lib/remote-deploy.ts +77 -0
- package/src/api/lib/ssh.ts +97 -0
- package/src/api/lib/sync-skills.ts +171 -0
- package/src/api/lib/tunnels.ts +7 -38
- package/src/api/routes/agents.ts +184 -132
- package/src/api/routes/documents.ts +24 -5
- package/src/api/routes/knowledge.ts +7 -5
- package/src/api/routes/machines.ts +107 -0
- package/src/api/routes/messages.ts +23 -19
- package/src/api/routes/repos.ts +74 -0
- package/src/api/routes/role-configs.ts +29 -46
- package/src/api/routes/skills.ts +198 -40
- package/src/api/routes/tasks.ts +24 -11
- package/src/cli/commands/init.ts +47 -18
- package/src/cli/commands/machines.ts +97 -0
- package/src/cli/commands/ps.ts +6 -4
- package/src/cli/commands/repos.ts +78 -0
- package/src/cli/commands/ssh.ts +14 -36
- package/src/cli/index.ts +5 -1
- package/src/daemon.ts +120 -1
- package/src/templates/solo/agents-config.md +39 -0
- package/src/templates/solo/agents.md +41 -0
- package/src/templates/solo/heartbeat.md +48 -0
- package/src/templates/solo/tools.md +35 -0
- package/dashboard/out/_next/static/chunks/app/layout-056f12675e691d12.js +0 -1
- package/dashboard/out/_next/static/chunks/app/page-80f01fdbb09b43c8.js +0 -1
- package/dashboard/out/_next/static/css/27d1ea794f04e96a.css +0 -1
- /package/dashboard/out/_next/static/{BRrkKiSxqTmex5oLQlOY5 → BIIuuS2pf7AQlPcSE2A6K}/_buildManifest.js +0 -0
- /package/dashboard/out/_next/static/{BRrkKiSxqTmex5oLQlOY5 → BIIuuS2pf7AQlPcSE2A6K}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { join } from "path";
|
|
2
|
+
import { existsSync, readdirSync, copyFileSync, mkdirSync } from "fs";
|
|
3
|
+
import { db } from "../db/client.js";
|
|
4
|
+
import { getSSHConfig } from "./ssh.js";
|
|
5
|
+
import { readMachines } from "../routes/machines.js";
|
|
6
|
+
import { listRoles } from "./fs-store.js";
|
|
7
|
+
|
|
8
|
+
export async function pushSkillsToAgent(agent: {
|
|
9
|
+
name: string;
|
|
10
|
+
role: string;
|
|
11
|
+
runtime: string;
|
|
12
|
+
machine?: string | null;
|
|
13
|
+
}): Promise<void> {
|
|
14
|
+
const isLocal = resolveIsLocal(agent.machine);
|
|
15
|
+
if (isLocal && agent.runtime === "system") return;
|
|
16
|
+
|
|
17
|
+
const skillsSource = join(process.cwd(), `roles/${agent.role}/skills/`);
|
|
18
|
+
if (!existsSync(skillsSource)) return;
|
|
19
|
+
|
|
20
|
+
if (agent.runtime === "system" && !isLocal) {
|
|
21
|
+
if (!agent.machine) return;
|
|
22
|
+
const config = await getSSHConfig(agent.machine);
|
|
23
|
+
const proc = Bun.spawn(
|
|
24
|
+
[
|
|
25
|
+
"rsync",
|
|
26
|
+
"-az",
|
|
27
|
+
"--delete",
|
|
28
|
+
"-e",
|
|
29
|
+
config.sshCmd,
|
|
30
|
+
skillsSource,
|
|
31
|
+
`${config.dest}:~/agency/roles/${agent.role}/skills/`,
|
|
32
|
+
],
|
|
33
|
+
{ stdout: "inherit", stderr: "inherit" },
|
|
34
|
+
);
|
|
35
|
+
const code = await proc.exited;
|
|
36
|
+
if (code !== 0) {
|
|
37
|
+
console.error(`[sync-skills] rsync to ${agent.name} failed (exit ${code})`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (agent.runtime === "docker") {
|
|
42
|
+
const proc = Bun.spawn(
|
|
43
|
+
[
|
|
44
|
+
"docker",
|
|
45
|
+
"cp",
|
|
46
|
+
`${skillsSource}.`,
|
|
47
|
+
`agent-${agent.name}:/app/roles/${agent.role}/skills`,
|
|
48
|
+
],
|
|
49
|
+
{ stdout: "inherit", stderr: "inherit" },
|
|
50
|
+
);
|
|
51
|
+
const code = await proc.exited;
|
|
52
|
+
if (code !== 0) {
|
|
53
|
+
console.error(`[sync-skills] docker cp to ${agent.name} failed (exit ${code})`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function pushRoleToAgent(agent: {
|
|
59
|
+
name: string;
|
|
60
|
+
role: string;
|
|
61
|
+
runtime: string;
|
|
62
|
+
machine?: string | null;
|
|
63
|
+
}): Promise<void> {
|
|
64
|
+
const isLocal = resolveIsLocal(agent.machine);
|
|
65
|
+
if (isLocal && agent.runtime === "system") return;
|
|
66
|
+
|
|
67
|
+
const roleSource = join(process.cwd(), `roles/${agent.role}/`);
|
|
68
|
+
if (!existsSync(roleSource)) return;
|
|
69
|
+
|
|
70
|
+
if (agent.runtime === "system" && !isLocal) {
|
|
71
|
+
if (!agent.machine) return;
|
|
72
|
+
const config = await getSSHConfig(agent.machine);
|
|
73
|
+
const proc = Bun.spawn(
|
|
74
|
+
[
|
|
75
|
+
"rsync",
|
|
76
|
+
"-az",
|
|
77
|
+
"--delete",
|
|
78
|
+
"-e",
|
|
79
|
+
config.sshCmd,
|
|
80
|
+
roleSource,
|
|
81
|
+
`${config.dest}:~/agency/roles/${agent.role}/`,
|
|
82
|
+
],
|
|
83
|
+
{ stdout: "inherit", stderr: "inherit" },
|
|
84
|
+
);
|
|
85
|
+
const code = await proc.exited;
|
|
86
|
+
if (code !== 0) {
|
|
87
|
+
console.error(`[sync] rsync role to ${agent.name} failed (exit ${code})`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (agent.runtime === "docker") {
|
|
92
|
+
const proc = Bun.spawn(
|
|
93
|
+
[
|
|
94
|
+
"docker",
|
|
95
|
+
"cp",
|
|
96
|
+
`${roleSource}.`,
|
|
97
|
+
`agent-${agent.name}:/app/roles/${agent.role}`,
|
|
98
|
+
],
|
|
99
|
+
{ stdout: "inherit", stderr: "inherit" },
|
|
100
|
+
);
|
|
101
|
+
const code = await proc.exited;
|
|
102
|
+
if (code !== 0) {
|
|
103
|
+
console.error(`[sync] docker cp role to ${agent.name} failed (exit ${code})`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export async function pushRoleToAllAgents(role: string): Promise<void> {
|
|
109
|
+
const agents = await db
|
|
110
|
+
.selectFrom("agents")
|
|
111
|
+
.where("status", "=", "active")
|
|
112
|
+
.where("role", "=", role)
|
|
113
|
+
.selectAll()
|
|
114
|
+
.execute();
|
|
115
|
+
|
|
116
|
+
await Promise.allSettled(
|
|
117
|
+
agents.map((a) =>
|
|
118
|
+
pushRoleToAgent({ name: a.name, role: a.role, runtime: (a as any).runtime ?? "system", machine: (a as any).machine }),
|
|
119
|
+
),
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export async function pushSkillsToAllAgents(): Promise<void> {
|
|
124
|
+
const agents = await db
|
|
125
|
+
.selectFrom("agents")
|
|
126
|
+
.where("status", "=", "active")
|
|
127
|
+
.selectAll()
|
|
128
|
+
.execute();
|
|
129
|
+
|
|
130
|
+
await Promise.allSettled(
|
|
131
|
+
agents.map((a) =>
|
|
132
|
+
pushSkillsToAgent({ name: a.name, role: a.role, runtime: (a as any).runtime ?? "system", machine: (a as any).machine }),
|
|
133
|
+
),
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Sync system-level files (from system/) to all role directories.
|
|
139
|
+
* These are Agency-managed files that apply to all agents regardless of role.
|
|
140
|
+
*/
|
|
141
|
+
export function syncSystemFilesToRoles(): void {
|
|
142
|
+
const systemDir = join(process.cwd(), "system");
|
|
143
|
+
if (!existsSync(systemDir)) return;
|
|
144
|
+
|
|
145
|
+
const roles = listRoles();
|
|
146
|
+
const systemFiles = readdirSync(systemDir).filter((f) => f.endsWith(".md"));
|
|
147
|
+
|
|
148
|
+
for (const role of roles) {
|
|
149
|
+
const roleDir = join(process.cwd(), "roles", role);
|
|
150
|
+
mkdirSync(roleDir, { recursive: true });
|
|
151
|
+
|
|
152
|
+
for (const file of systemFiles) {
|
|
153
|
+
const src = join(systemDir, file);
|
|
154
|
+
const dest = join(roleDir, file);
|
|
155
|
+
try {
|
|
156
|
+
copyFileSync(src, dest);
|
|
157
|
+
} catch (err) {
|
|
158
|
+
console.error(`[sync-system] failed to copy ${file} to ${role}:`, err);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
console.log(`[sync-system] synced ${systemFiles.length} system file(s) to ${roles.length} role(s)`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function resolveIsLocal(machineName?: string | null): boolean {
|
|
167
|
+
if (!machineName) return true;
|
|
168
|
+
const machines = readMachines();
|
|
169
|
+
const machine = machines.find((m) => m.name === machineName);
|
|
170
|
+
return machine?.auth === "local" ?? true;
|
|
171
|
+
}
|
package/src/api/lib/tunnels.ts
CHANGED
|
@@ -1,58 +1,27 @@
|
|
|
1
1
|
import type { Subprocess } from "bun";
|
|
2
|
-
import
|
|
3
|
-
import * as path from "path";
|
|
4
|
-
import * as os from "os";
|
|
5
|
-
import { db } from "../db/client.js";
|
|
2
|
+
import { getSSHConfig } from "./ssh.js";
|
|
6
3
|
|
|
7
4
|
const tunnels = new Map<string, Subprocess>();
|
|
8
5
|
|
|
9
6
|
const API_PORT = Number(process.env.PORT ?? 3100);
|
|
10
7
|
|
|
11
|
-
async function
|
|
12
|
-
const rows = await db
|
|
13
|
-
.selectFrom("settings")
|
|
14
|
-
.where("category", "=", "ssh")
|
|
15
|
-
.selectAll()
|
|
16
|
-
.execute();
|
|
17
|
-
|
|
18
|
-
const settings: Record<string, string> = {};
|
|
19
|
-
for (const r of rows) settings[r.key] = r.value;
|
|
20
|
-
|
|
21
|
-
const user = settings["ssh.user"] || "ubuntu";
|
|
22
|
-
const privateKey = settings["ssh.private_key"] || "";
|
|
23
|
-
|
|
24
|
-
if (!privateKey) {
|
|
25
|
-
throw new Error("SSH private key not configured. Set it in Settings → SSH.");
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Write key to a temp file (SSH requires a file path)
|
|
29
|
-
const keyDir = path.join(os.tmpdir(), "agency-ssh");
|
|
30
|
-
fs.mkdirSync(keyDir, { recursive: true, mode: 0o700 });
|
|
31
|
-
const keyPath = path.join(keyDir, "agent_key");
|
|
32
|
-
fs.writeFileSync(keyPath, privateKey + "\n", { mode: 0o600 });
|
|
33
|
-
|
|
34
|
-
return { keyPath, user };
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export async function startTunnel(name: string, host: string): Promise<void> {
|
|
8
|
+
export async function startTunnel(name: string, host: string, machineName?: string): Promise<void> {
|
|
38
9
|
// Kill existing tunnel if any
|
|
39
10
|
stopTunnel(name);
|
|
40
11
|
|
|
41
|
-
const
|
|
12
|
+
const config = await getSSHConfig(machineName);
|
|
42
13
|
|
|
43
14
|
const args = [
|
|
44
|
-
|
|
45
|
-
"-i", keyPath,
|
|
46
|
-
"-o", "StrictHostKeyChecking=no",
|
|
15
|
+
...config.args,
|
|
47
16
|
"-o", "ServerAliveInterval=30",
|
|
48
17
|
"-o", "ServerAliveCountMax=3",
|
|
49
18
|
"-o", "ExitOnForwardFailure=yes",
|
|
50
19
|
"-N", // no command, just tunnel
|
|
51
20
|
"-R", `${API_PORT}:localhost:${API_PORT}`, // reverse tunnel: remote:3100 -> local:3100
|
|
52
|
-
|
|
21
|
+
config.dest,
|
|
53
22
|
];
|
|
54
23
|
|
|
55
|
-
console.log(`[tunnel] opening reverse tunnel to ${name} (${
|
|
24
|
+
console.log(`[tunnel] opening reverse tunnel to ${name} (${config.dest})`);
|
|
56
25
|
|
|
57
26
|
const proc = Bun.spawn(args, {
|
|
58
27
|
stdout: "inherit",
|
|
@@ -69,7 +38,7 @@ export async function startTunnel(name: string, host: string): Promise<void> {
|
|
|
69
38
|
setTimeout(() => {
|
|
70
39
|
// Only restart if no new tunnel was created
|
|
71
40
|
if (!tunnels.has(name)) {
|
|
72
|
-
startTunnel(name, host).catch((err) =>
|
|
41
|
+
startTunnel(name, host, machineName).catch((err) =>
|
|
73
42
|
console.error(`[tunnel] failed to restart tunnel for ${name}:`, err.message)
|
|
74
43
|
);
|
|
75
44
|
}
|