@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.
Files changed (62) hide show
  1. package/README.md +120 -52
  2. package/dashboard/out/404.html +1 -1
  3. package/dashboard/out/_next/static/chunks/app/_not-found/{page-ad40673d821037f6.js → page-5cb94002960ab71a.js} +1 -1
  4. package/dashboard/out/_next/static/chunks/app/layout-6249f74085ad56b1.js +1 -0
  5. package/dashboard/out/_next/static/chunks/app/page-0a5ee03ddf4553ab.js +1 -0
  6. package/dashboard/out/_next/static/chunks/{main-app-1d848b791b823fa6.js → main-app-0398d52862f5c730.js} +1 -1
  7. package/dashboard/out/_next/static/css/a13af72b10a7d74f.css +1 -0
  8. package/dashboard/out/index.html +1 -1
  9. package/dashboard/out/index.txt +4 -4
  10. package/docs/images/agency_cli_ps.png +0 -0
  11. package/docs/images/agency_ui_ai_prodivder_settings.png +0 -0
  12. package/docs/images/agency_ui_aws_settings.png +0 -0
  13. package/docs/images/agency_ui_identity_settings.png +0 -0
  14. package/docs/images/agency_ui_import_skills.jpeg +0 -0
  15. package/docs/images/agency_ui_knowledge.png +0 -0
  16. package/docs/images/agency_ui_mission_control.png +0 -0
  17. package/docs/images/agency_ui_skills_marketplace.png +0 -0
  18. package/docs/images/agent_ui_agent_config.png +0 -0
  19. package/package.json +1 -1
  20. package/src/api/db/migrations/004_nullable_human_refs.ts +129 -0
  21. package/src/api/db/migrations/005_agent_skills.ts +14 -0
  22. package/src/api/db/migrations/006_runtime_machine.ts +24 -0
  23. package/src/api/db/seed.ts +62 -46
  24. package/src/api/index.ts +11 -4
  25. package/src/api/lib/deploy.ts +412 -0
  26. package/src/api/lib/env-vars.ts +19 -0
  27. package/src/api/lib/exec.ts +77 -0
  28. package/src/api/lib/fleet-sync.ts +49 -32
  29. package/src/api/lib/fs-store.ts +350 -0
  30. package/src/api/lib/import-skills.ts +105 -0
  31. package/src/api/lib/metrics.ts +183 -0
  32. package/src/api/lib/processes.ts +82 -12
  33. package/src/api/lib/provision-openclaw.ts +407 -0
  34. package/src/api/lib/remote-deploy.ts +77 -0
  35. package/src/api/lib/ssh.ts +97 -0
  36. package/src/api/lib/sync-skills.ts +171 -0
  37. package/src/api/lib/tunnels.ts +7 -38
  38. package/src/api/routes/agents.ts +184 -132
  39. package/src/api/routes/documents.ts +24 -5
  40. package/src/api/routes/knowledge.ts +7 -5
  41. package/src/api/routes/machines.ts +107 -0
  42. package/src/api/routes/messages.ts +23 -19
  43. package/src/api/routes/repos.ts +74 -0
  44. package/src/api/routes/role-configs.ts +29 -46
  45. package/src/api/routes/skills.ts +198 -40
  46. package/src/api/routes/tasks.ts +24 -11
  47. package/src/cli/commands/init.ts +47 -18
  48. package/src/cli/commands/machines.ts +97 -0
  49. package/src/cli/commands/ps.ts +6 -4
  50. package/src/cli/commands/repos.ts +78 -0
  51. package/src/cli/commands/ssh.ts +14 -36
  52. package/src/cli/index.ts +5 -1
  53. package/src/daemon.ts +120 -1
  54. package/src/templates/solo/agents-config.md +39 -0
  55. package/src/templates/solo/agents.md +41 -0
  56. package/src/templates/solo/heartbeat.md +48 -0
  57. package/src/templates/solo/tools.md +35 -0
  58. package/dashboard/out/_next/static/chunks/app/layout-056f12675e691d12.js +0 -1
  59. package/dashboard/out/_next/static/chunks/app/page-80f01fdbb09b43c8.js +0 -1
  60. package/dashboard/out/_next/static/css/27d1ea794f04e96a.css +0 -1
  61. /package/dashboard/out/_next/static/{BRrkKiSxqTmex5oLQlOY5 → BIIuuS2pf7AQlPcSE2A6K}/_buildManifest.js +0 -0
  62. /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
+ }
@@ -1,58 +1,27 @@
1
1
  import type { Subprocess } from "bun";
2
- import * as fs from "fs";
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 getSSHConfig(): Promise<{ keyPath: string; user: string }> {
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 { keyPath, user } = await getSSHConfig();
12
+ const config = await getSSHConfig(machineName);
42
13
 
43
14
  const args = [
44
- "ssh",
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
- `${user}@${host}`,
21
+ config.dest,
53
22
  ];
54
23
 
55
- console.log(`[tunnel] opening reverse tunnel to ${name} (${user}@${host})`);
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
  }