@jx0/agency 0.2.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/README.md +272 -0
- package/bin/agency.js +2 -0
- package/dashboard/out/404.html +1 -0
- package/dashboard/out/_next/static/chunks/255-67e8754147461423.js +1 -0
- package/dashboard/out/_next/static/chunks/4bd1b696-c023c6e3521b1417.js +1 -0
- package/dashboard/out/_next/static/chunks/app/_not-found/page-ad40673d821037f6.js +1 -0
- package/dashboard/out/_next/static/chunks/app/layout-056f12675e691d12.js +1 -0
- package/dashboard/out/_next/static/chunks/app/page-80f01fdbb09b43c8.js +1 -0
- package/dashboard/out/_next/static/chunks/framework-de98b93a850cfc71.js +1 -0
- package/dashboard/out/_next/static/chunks/main-1a0dcce460eb61ce.js +1 -0
- package/dashboard/out/_next/static/chunks/main-app-1d848b791b823fa6.js +1 -0
- package/dashboard/out/_next/static/chunks/pages/_app-7d307437aca18ad4.js +1 -0
- package/dashboard/out/_next/static/chunks/pages/_error-cb2a52f75f2162e2.js +1 -0
- package/dashboard/out/_next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
- package/dashboard/out/_next/static/chunks/webpack-4e6bf084ac60582b.js +1 -0
- package/dashboard/out/_next/static/css/27d1ea794f04e96a.css +1 -0
- package/dashboard/out/_next/static/pU1nwWH_dNUOCI8y4nl3C/_buildManifest.js +1 -0
- package/dashboard/out/_next/static/pU1nwWH_dNUOCI8y4nl3C/_ssgManifest.js +1 -0
- package/dashboard/out/index.html +1 -0
- package/dashboard/out/index.txt +19 -0
- 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_mission_control.png +0 -0
- package/docs/images/agent_ui_agent_config.png +0 -0
- package/package.json +31 -0
- package/src/api/db/client.ts +16 -0
- package/src/api/db/migrate.ts +37 -0
- package/src/api/db/migrations/001_initial.ts +193 -0
- package/src/api/db/migrations/002_configs.ts +76 -0
- package/src/api/db/migrations/003_settings_columns.ts +13 -0
- package/src/api/db/seed.ts +142 -0
- package/src/api/db/types.ts +126 -0
- package/src/api/index.ts +73 -0
- package/src/api/lib/activity.ts +13 -0
- package/src/api/lib/fleet-sync.ts +156 -0
- package/src/api/lib/mentions.ts +59 -0
- package/src/api/lib/processes.ts +45 -0
- package/src/api/lib/resolve-agent.ts +5 -0
- package/src/api/lib/tunnels.ts +99 -0
- package/src/api/routes/activities.ts +27 -0
- package/src/api/routes/agents.ts +311 -0
- package/src/api/routes/documents.ts +41 -0
- package/src/api/routes/knowledge.ts +60 -0
- package/src/api/routes/messages.ts +54 -0
- package/src/api/routes/notifications.ts +40 -0
- package/src/api/routes/oauth.ts +171 -0
- package/src/api/routes/role-configs.ts +71 -0
- package/src/api/routes/settings.ts +94 -0
- package/src/api/routes/skills.ts +76 -0
- package/src/api/routes/tasks.ts +154 -0
- package/src/cli/commands/config.ts +42 -0
- package/src/cli/commands/daemon.ts +173 -0
- package/src/cli/commands/doc.ts +47 -0
- package/src/cli/commands/init.ts +105 -0
- package/src/cli/commands/learn.ts +51 -0
- package/src/cli/commands/logs.ts +31 -0
- package/src/cli/commands/msg.ts +18 -0
- package/src/cli/commands/ps.ts +19 -0
- package/src/cli/commands/recall.ts +18 -0
- package/src/cli/commands/skills.ts +66 -0
- package/src/cli/commands/ssh.ts +68 -0
- package/src/cli/commands/start.ts +14 -0
- package/src/cli/commands/status.ts +33 -0
- package/src/cli/commands/stop.ts +11 -0
- package/src/cli/commands/tasks.ts +150 -0
- package/src/cli/index.ts +70 -0
- package/src/cli/lib/api.ts +16 -0
- package/src/cli/lib/config.ts +5 -0
- package/src/cli/lib/find-root.ts +32 -0
- package/src/cli/lib/prompt.ts +20 -0
- package/src/daemon.ts +83 -0
- package/src/templates/implementer/agents-config.md +44 -0
- package/src/templates/implementer/agents.md +32 -0
- package/src/templates/implementer/heartbeat.md +47 -0
- package/src/templates/implementer/tools.md +33 -0
- package/src/templates/orchestrator/agents-config.md +44 -0
- package/src/templates/orchestrator/agents.md +27 -0
- package/src/templates/orchestrator/heartbeat.md +40 -0
- package/src/templates/orchestrator/tools.md +40 -0
- package/src/templates/shared/environment.md +20 -0
- package/src/templates/shared/memory.md +20 -0
- package/src/templates/shared/soul.md +26 -0
- package/src/templates/shared/user.md +12 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import type { Kysely } from "kysely";
|
|
2
|
+
import { sql } from "kysely";
|
|
3
|
+
|
|
4
|
+
export async function up(db: Kysely<any>): Promise<void> {
|
|
5
|
+
await db.schema
|
|
6
|
+
.createTable("agents")
|
|
7
|
+
.addColumn("id", "text", (col) => col.primaryKey())
|
|
8
|
+
.addColumn("name", "text", (col) => col.notNull().unique())
|
|
9
|
+
.addColumn("role", "text", (col) => col.notNull())
|
|
10
|
+
.addColumn("status", "text", (col) => col.notNull().defaultTo("idle"))
|
|
11
|
+
.addColumn("current_task", "text")
|
|
12
|
+
.addColumn("location", "text")
|
|
13
|
+
.addColumn("slack_bot_token", "text")
|
|
14
|
+
.addColumn("slack_app_token", "text")
|
|
15
|
+
.addColumn("session_key", "text", (col) => col.notNull().unique())
|
|
16
|
+
.addColumn("created_at", "text", (col) =>
|
|
17
|
+
col.notNull().defaultTo(sql`(datetime('now'))`)
|
|
18
|
+
)
|
|
19
|
+
.addColumn("updated_at", "text", (col) =>
|
|
20
|
+
col.notNull().defaultTo(sql`(datetime('now'))`)
|
|
21
|
+
)
|
|
22
|
+
.execute();
|
|
23
|
+
|
|
24
|
+
await db.schema
|
|
25
|
+
.createTable("tasks")
|
|
26
|
+
.addColumn("id", "text", (col) => col.primaryKey())
|
|
27
|
+
.addColumn("title", "text", (col) => col.notNull())
|
|
28
|
+
.addColumn("description", "text", (col) => col.notNull())
|
|
29
|
+
.addColumn("design", "text")
|
|
30
|
+
.addColumn("acceptance", "text")
|
|
31
|
+
.addColumn("status", "text", (col) => col.notNull().defaultTo("backlog"))
|
|
32
|
+
.addColumn("priority", "integer", (col) => col.notNull().defaultTo(0))
|
|
33
|
+
.addColumn("task_type", "text", (col) => col.notNull().defaultTo("task"))
|
|
34
|
+
.addColumn("parent_id", "text", (col) =>
|
|
35
|
+
col.references("tasks.id").onDelete("set null")
|
|
36
|
+
)
|
|
37
|
+
.addColumn("created_by", "text", (col) =>
|
|
38
|
+
col.notNull().references("agents.id")
|
|
39
|
+
)
|
|
40
|
+
.addColumn("created_at", "text", (col) =>
|
|
41
|
+
col.notNull().defaultTo(sql`(datetime('now'))`)
|
|
42
|
+
)
|
|
43
|
+
.addColumn("updated_at", "text", (col) =>
|
|
44
|
+
col.notNull().defaultTo(sql`(datetime('now'))`)
|
|
45
|
+
)
|
|
46
|
+
.execute();
|
|
47
|
+
|
|
48
|
+
await db.schema
|
|
49
|
+
.createTable("task_assignees")
|
|
50
|
+
.addColumn("task_id", "text", (col) =>
|
|
51
|
+
col.notNull().references("tasks.id").onDelete("cascade")
|
|
52
|
+
)
|
|
53
|
+
.addColumn("agent_id", "text", (col) =>
|
|
54
|
+
col.notNull().references("agents.id").onDelete("cascade")
|
|
55
|
+
)
|
|
56
|
+
.addPrimaryKeyConstraint("pk_task_assignees", ["task_id", "agent_id"])
|
|
57
|
+
.execute();
|
|
58
|
+
|
|
59
|
+
await db.schema
|
|
60
|
+
.createTable("messages")
|
|
61
|
+
.addColumn("id", "text", (col) => col.primaryKey())
|
|
62
|
+
.addColumn("task_id", "text", (col) =>
|
|
63
|
+
col.notNull().references("tasks.id").onDelete("cascade")
|
|
64
|
+
)
|
|
65
|
+
.addColumn("from_agent", "text", (col) =>
|
|
66
|
+
col.notNull().references("agents.id")
|
|
67
|
+
)
|
|
68
|
+
.addColumn("content", "text", (col) => col.notNull())
|
|
69
|
+
.addColumn("created_at", "text", (col) =>
|
|
70
|
+
col.notNull().defaultTo(sql`(datetime('now'))`)
|
|
71
|
+
)
|
|
72
|
+
.execute();
|
|
73
|
+
|
|
74
|
+
await db.schema
|
|
75
|
+
.createTable("activities")
|
|
76
|
+
.addColumn("id", "text", (col) => col.primaryKey())
|
|
77
|
+
.addColumn("type", "text", (col) => col.notNull())
|
|
78
|
+
.addColumn("agent_id", "text", (col) =>
|
|
79
|
+
col.notNull().references("agents.id")
|
|
80
|
+
)
|
|
81
|
+
.addColumn("task_id", "text", (col) =>
|
|
82
|
+
col.references("tasks.id").onDelete("set null")
|
|
83
|
+
)
|
|
84
|
+
.addColumn("summary", "text", (col) => col.notNull())
|
|
85
|
+
.addColumn("created_at", "text", (col) =>
|
|
86
|
+
col.notNull().defaultTo(sql`(datetime('now'))`)
|
|
87
|
+
)
|
|
88
|
+
.execute();
|
|
89
|
+
|
|
90
|
+
await db.schema
|
|
91
|
+
.createTable("notifications")
|
|
92
|
+
.addColumn("id", "text", (col) => col.primaryKey())
|
|
93
|
+
.addColumn("target_agent", "text", (col) =>
|
|
94
|
+
col.notNull().references("agents.id")
|
|
95
|
+
)
|
|
96
|
+
.addColumn("source_agent", "text", (col) =>
|
|
97
|
+
col.references("agents.id")
|
|
98
|
+
)
|
|
99
|
+
.addColumn("task_id", "text", (col) =>
|
|
100
|
+
col.references("tasks.id").onDelete("set null")
|
|
101
|
+
)
|
|
102
|
+
.addColumn("content", "text", (col) => col.notNull())
|
|
103
|
+
.addColumn("delivered", "integer", (col) =>
|
|
104
|
+
col.notNull().defaultTo(0)
|
|
105
|
+
)
|
|
106
|
+
.addColumn("created_at", "text", (col) =>
|
|
107
|
+
col.notNull().defaultTo(sql`(datetime('now'))`)
|
|
108
|
+
)
|
|
109
|
+
.execute();
|
|
110
|
+
|
|
111
|
+
await db.schema
|
|
112
|
+
.createTable("documents")
|
|
113
|
+
.addColumn("id", "text", (col) => col.primaryKey())
|
|
114
|
+
.addColumn("title", "text", (col) => col.notNull())
|
|
115
|
+
.addColumn("content", "text", (col) => col.notNull())
|
|
116
|
+
.addColumn("doc_type", "text", (col) => col.notNull())
|
|
117
|
+
.addColumn("task_id", "text", (col) =>
|
|
118
|
+
col.references("tasks.id").onDelete("set null")
|
|
119
|
+
)
|
|
120
|
+
.addColumn("created_by", "text", (col) =>
|
|
121
|
+
col.notNull().references("agents.id")
|
|
122
|
+
)
|
|
123
|
+
.addColumn("created_at", "text", (col) =>
|
|
124
|
+
col.notNull().defaultTo(sql`(datetime('now'))`)
|
|
125
|
+
)
|
|
126
|
+
.addColumn("updated_at", "text", (col) =>
|
|
127
|
+
col.notNull().defaultTo(sql`(datetime('now'))`)
|
|
128
|
+
)
|
|
129
|
+
.execute();
|
|
130
|
+
|
|
131
|
+
await db.schema
|
|
132
|
+
.createTable("knowledge")
|
|
133
|
+
.addColumn("id", "text", (col) => col.primaryKey())
|
|
134
|
+
.addColumn("key", "text", (col) => col.notNull().unique())
|
|
135
|
+
.addColumn("content", "text", (col) => col.notNull())
|
|
136
|
+
.addColumn("source", "text", (col) => col.notNull())
|
|
137
|
+
.addColumn("task_id", "text", (col) =>
|
|
138
|
+
col.references("tasks.id").onDelete("set null")
|
|
139
|
+
)
|
|
140
|
+
.addColumn("tags", "text", (col) => col.notNull().defaultTo("[]"))
|
|
141
|
+
.addColumn("created_at", "text", (col) =>
|
|
142
|
+
col.notNull().defaultTo(sql`(datetime('now'))`)
|
|
143
|
+
)
|
|
144
|
+
.execute();
|
|
145
|
+
|
|
146
|
+
// Indexes
|
|
147
|
+
await db.schema
|
|
148
|
+
.createIndex("idx_tasks_status")
|
|
149
|
+
.on("tasks")
|
|
150
|
+
.column("status")
|
|
151
|
+
.execute();
|
|
152
|
+
|
|
153
|
+
await db.schema
|
|
154
|
+
.createIndex("idx_tasks_parent")
|
|
155
|
+
.on("tasks")
|
|
156
|
+
.column("parent_id")
|
|
157
|
+
.execute();
|
|
158
|
+
|
|
159
|
+
await db.schema
|
|
160
|
+
.createIndex("idx_messages_task")
|
|
161
|
+
.on("messages")
|
|
162
|
+
.column("task_id")
|
|
163
|
+
.execute();
|
|
164
|
+
|
|
165
|
+
await db.schema
|
|
166
|
+
.createIndex("idx_activities_created")
|
|
167
|
+
.on("activities")
|
|
168
|
+
.column("created_at")
|
|
169
|
+
.execute();
|
|
170
|
+
|
|
171
|
+
await db.schema
|
|
172
|
+
.createIndex("idx_notifications_pending")
|
|
173
|
+
.on("notifications")
|
|
174
|
+
.columns(["target_agent", "delivered"])
|
|
175
|
+
.execute();
|
|
176
|
+
|
|
177
|
+
await db.schema
|
|
178
|
+
.createIndex("idx_knowledge_key")
|
|
179
|
+
.on("knowledge")
|
|
180
|
+
.column("key")
|
|
181
|
+
.execute();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export async function down(db: Kysely<any>): Promise<void> {
|
|
185
|
+
await db.schema.dropTable("knowledge").ifExists().execute();
|
|
186
|
+
await db.schema.dropTable("documents").ifExists().execute();
|
|
187
|
+
await db.schema.dropTable("notifications").ifExists().execute();
|
|
188
|
+
await db.schema.dropTable("activities").ifExists().execute();
|
|
189
|
+
await db.schema.dropTable("messages").ifExists().execute();
|
|
190
|
+
await db.schema.dropTable("task_assignees").ifExists().execute();
|
|
191
|
+
await db.schema.dropTable("tasks").ifExists().execute();
|
|
192
|
+
await db.schema.dropTable("agents").ifExists().execute();
|
|
193
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { Kysely } from "kysely";
|
|
2
|
+
import { sql } from "kysely";
|
|
3
|
+
|
|
4
|
+
export async function up(db: Kysely<any>): Promise<void> {
|
|
5
|
+
await db.schema
|
|
6
|
+
.createTable("settings")
|
|
7
|
+
.addColumn("key", "text", (col) => col.primaryKey())
|
|
8
|
+
.addColumn("value", "text", (col) => col.notNull())
|
|
9
|
+
.addColumn("category", "text", (col) => col.notNull().defaultTo("general"))
|
|
10
|
+
.addColumn("description", "text")
|
|
11
|
+
.addColumn("updated_at", "text", (col) =>
|
|
12
|
+
col.notNull().defaultTo(sql`(datetime('now'))`)
|
|
13
|
+
)
|
|
14
|
+
.execute();
|
|
15
|
+
|
|
16
|
+
await db.schema
|
|
17
|
+
.createTable("skills")
|
|
18
|
+
.addColumn("id", "text", (col) => col.primaryKey())
|
|
19
|
+
.addColumn("name", "text", (col) => col.notNull().unique())
|
|
20
|
+
.addColumn("body", "text", (col) => col.notNull())
|
|
21
|
+
.addColumn("category", "text", (col) => col.notNull().defaultTo("general"))
|
|
22
|
+
.addColumn("tags", "text", (col) => col.notNull().defaultTo("[]"))
|
|
23
|
+
.addColumn("created_at", "text", (col) =>
|
|
24
|
+
col.notNull().defaultTo(sql`(datetime('now'))`)
|
|
25
|
+
)
|
|
26
|
+
.addColumn("updated_at", "text", (col) =>
|
|
27
|
+
col.notNull().defaultTo(sql`(datetime('now'))`)
|
|
28
|
+
)
|
|
29
|
+
.execute();
|
|
30
|
+
|
|
31
|
+
await db.schema
|
|
32
|
+
.createTable("role_configs")
|
|
33
|
+
.addColumn("id", "text", (col) => col.primaryKey())
|
|
34
|
+
.addColumn("role", "text", (col) => col.notNull())
|
|
35
|
+
.addColumn("config_type", "text", (col) => col.notNull())
|
|
36
|
+
.addColumn("content", "text", (col) => col.notNull())
|
|
37
|
+
.addColumn("created_at", "text", (col) =>
|
|
38
|
+
col.notNull().defaultTo(sql`(datetime('now'))`)
|
|
39
|
+
)
|
|
40
|
+
.addColumn("updated_at", "text", (col) =>
|
|
41
|
+
col.notNull().defaultTo(sql`(datetime('now'))`)
|
|
42
|
+
)
|
|
43
|
+
.execute();
|
|
44
|
+
|
|
45
|
+
// Unique constraint on role + config_type
|
|
46
|
+
await db.schema
|
|
47
|
+
.createIndex("idx_role_configs_unique")
|
|
48
|
+
.on("role_configs")
|
|
49
|
+
.columns(["role", "config_type"])
|
|
50
|
+
.unique()
|
|
51
|
+
.execute();
|
|
52
|
+
|
|
53
|
+
await db.schema
|
|
54
|
+
.createIndex("idx_settings_category")
|
|
55
|
+
.on("settings")
|
|
56
|
+
.column("category")
|
|
57
|
+
.execute();
|
|
58
|
+
|
|
59
|
+
await db.schema
|
|
60
|
+
.createIndex("idx_skills_category")
|
|
61
|
+
.on("skills")
|
|
62
|
+
.column("category")
|
|
63
|
+
.execute();
|
|
64
|
+
|
|
65
|
+
await db.schema
|
|
66
|
+
.createIndex("idx_role_configs_role")
|
|
67
|
+
.on("role_configs")
|
|
68
|
+
.column("role")
|
|
69
|
+
.execute();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export async function down(db: Kysely<any>): Promise<void> {
|
|
73
|
+
await db.schema.dropTable("role_configs").ifExists().execute();
|
|
74
|
+
await db.schema.dropTable("skills").ifExists().execute();
|
|
75
|
+
await db.schema.dropTable("settings").ifExists().execute();
|
|
76
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Kysely } from "kysely";
|
|
2
|
+
import { sql } from "kysely";
|
|
3
|
+
|
|
4
|
+
export async function up(db: Kysely<any>): Promise<void> {
|
|
5
|
+
await sql`ALTER TABLE settings ADD COLUMN sensitive INTEGER NOT NULL DEFAULT 0`.execute(db);
|
|
6
|
+
await sql`ALTER TABLE settings ADD COLUMN input_type TEXT NOT NULL DEFAULT 'text'`.execute(db);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export async function down(db: Kysely<any>): Promise<void> {
|
|
10
|
+
// SQLite doesn't support DROP COLUMN before 3.35.0; recreate table if needed
|
|
11
|
+
await sql`ALTER TABLE settings DROP COLUMN sensitive`.execute(db);
|
|
12
|
+
await sql`ALTER TABLE settings DROP COLUMN input_type`.execute(db);
|
|
13
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { db } from "./client.js";
|
|
2
|
+
import * as fs from "fs";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
|
|
5
|
+
const TEMPLATES_DIR = path.join(import.meta.dir, "../../templates");
|
|
6
|
+
|
|
7
|
+
function readTemplate(...segments: string[]): string {
|
|
8
|
+
const filePath = path.join(TEMPLATES_DIR, ...segments);
|
|
9
|
+
return fs.readFileSync(filePath, "utf-8");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface SeedOptions {
|
|
13
|
+
userName?: string;
|
|
14
|
+
teamName?: string;
|
|
15
|
+
roles?: string[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function seedDefaults(options: SeedOptions = {}) {
|
|
19
|
+
const userName = options.userName ?? "Human";
|
|
20
|
+
const teamName = options.teamName ?? "My Team";
|
|
21
|
+
const roles = options.roles ?? ["orchestrator", "implementer"];
|
|
22
|
+
|
|
23
|
+
// Seed default settings
|
|
24
|
+
const defaultSettings: {
|
|
25
|
+
key: string;
|
|
26
|
+
value: string;
|
|
27
|
+
category: string;
|
|
28
|
+
description: string;
|
|
29
|
+
sensitive: number;
|
|
30
|
+
input_type: string;
|
|
31
|
+
}[] = [
|
|
32
|
+
// Identity
|
|
33
|
+
{ key: "user.name", value: userName, category: "identity", description: "Human operator name", sensitive: 0, input_type: "text" },
|
|
34
|
+
{ key: "user.email", value: "", category: "identity", description: "User email", sensitive: 0, input_type: "text" },
|
|
35
|
+
{ key: "team.name", value: teamName, category: "identity", description: "Team name", sensitive: 0, input_type: "text" },
|
|
36
|
+
// AI
|
|
37
|
+
{ key: "ai.anthropic_api_key", value: "", category: "ai", description: "Anthropic API key", sensitive: 1, input_type: "password" },
|
|
38
|
+
{ key: "ai.auth_method", value: "api_key", category: "ai", description: "Auth method: api_key or oauth", sensitive: 0, input_type: "text" },
|
|
39
|
+
{ key: "ai.oauth_access_token", value: "", category: "ai", description: "Claude OAuth access token", sensitive: 1, input_type: "password" },
|
|
40
|
+
{ key: "ai.oauth_refresh_token", value: "", category: "ai", description: "Claude OAuth refresh token", sensitive: 1, input_type: "password" },
|
|
41
|
+
{ key: "ai.oauth_expires_at", value: "", category: "ai", description: "Token expiry (ISO string)", sensitive: 0, input_type: "readonly" },
|
|
42
|
+
{ key: "ai.oauth_subscription_type", value: "", category: "ai", description: "Subscription type (max, pro, etc.)", sensitive: 0, input_type: "readonly" },
|
|
43
|
+
// AWS
|
|
44
|
+
{ key: "aws.access_key_id", value: "", category: "aws", description: "AWS Access Key ID", sensitive: 1, input_type: "password" },
|
|
45
|
+
{ key: "aws.secret_access_key", value: "", category: "aws", description: "AWS Secret Access Key", sensitive: 1, input_type: "password" },
|
|
46
|
+
{ key: "aws.region", value: "", category: "aws", description: "AWS region", sensitive: 0, input_type: "text" },
|
|
47
|
+
{ key: "aws.ami_id", value: "", category: "aws", description: "AMI for EC2 agents", sensitive: 0, input_type: "text" },
|
|
48
|
+
{ key: "aws.instance_type", value: "", category: "aws", description: "Default EC2 instance type", sensitive: 0, input_type: "text" },
|
|
49
|
+
{ key: "aws.s3_bucket_prefix", value: "", category: "aws", description: "S3 bucket prefix", sensitive: 0, input_type: "text" },
|
|
50
|
+
// SSH
|
|
51
|
+
{ key: "ssh.private_key", value: "", category: "ssh", description: "SSH private key content", sensitive: 1, input_type: "textarea" },
|
|
52
|
+
{ key: "ssh.public_key", value: "", category: "ssh", description: "SSH public key (for display/copy)", sensitive: 0, input_type: "textarea" },
|
|
53
|
+
{ key: "ssh.key_name", value: "", category: "ssh", description: "SSH key name", sensitive: 0, input_type: "text" },
|
|
54
|
+
{ key: "ssh.user", value: "", category: "ssh", description: "SSH username", sensitive: 0, input_type: "text" },
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
for (const s of defaultSettings) {
|
|
58
|
+
const existing = await db.selectFrom("settings").where("key", "=", s.key).selectAll().executeTakeFirst();
|
|
59
|
+
if (!existing) {
|
|
60
|
+
await db.insertInto("settings").values(s).execute();
|
|
61
|
+
console.log(`[seed] setting: ${s.key}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Clean up removed settings
|
|
66
|
+
const removedKeys = ["slack.team_channel", "slack.human_user_id", "ssh.key_path", "aws.profile"];
|
|
67
|
+
for (const key of removedKeys) {
|
|
68
|
+
await db.deleteFrom("settings").where("key", "=", key).execute();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Migrate old categories
|
|
72
|
+
await db.updateTable("settings").where("key", "=", "user.name").set({ category: "identity" }).execute();
|
|
73
|
+
await db.updateTable("settings").where("key", "=", "team.name").set({ category: "identity" }).execute();
|
|
74
|
+
|
|
75
|
+
// Helper to replace template variables
|
|
76
|
+
function interpolate(content: string): string {
|
|
77
|
+
return content
|
|
78
|
+
.replace(/\{\{user\.name\}\}/g, userName)
|
|
79
|
+
.replace(/\{\{team\.name\}\}/g, teamName);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Seed role configs for each role
|
|
83
|
+
const ROLE_SPECIFIC = ["heartbeat", "agents-config", "tools", "agents"];
|
|
84
|
+
const SHARED_CONFIGS: { configType: string; template: string }[] = [
|
|
85
|
+
{ configType: "environment", template: "environment.md" },
|
|
86
|
+
{ configType: "soul", template: "soul.md" },
|
|
87
|
+
{ configType: "identity", template: "user.md" },
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
for (const role of roles) {
|
|
91
|
+
const templateRole = fs.existsSync(path.join(TEMPLATES_DIR, role)) ? role : "implementer";
|
|
92
|
+
const configs = [
|
|
93
|
+
...ROLE_SPECIFIC.map((ct) => ({ configType: ct, segments: [templateRole, `${ct}.md`] })),
|
|
94
|
+
...SHARED_CONFIGS.map((c) => ({ configType: c.configType, segments: ["shared", c.template] })),
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
for (const { configType, segments } of configs) {
|
|
98
|
+
const existing = await db
|
|
99
|
+
.selectFrom("role_configs")
|
|
100
|
+
.where("role", "=", role)
|
|
101
|
+
.where("config_type", "=", configType)
|
|
102
|
+
.selectAll()
|
|
103
|
+
.executeTakeFirst();
|
|
104
|
+
|
|
105
|
+
if (!existing) {
|
|
106
|
+
const content = interpolate(readTemplate(...segments));
|
|
107
|
+
await db.insertInto("role_configs").values({
|
|
108
|
+
id: crypto.randomUUID(),
|
|
109
|
+
role,
|
|
110
|
+
config_type: configType,
|
|
111
|
+
content,
|
|
112
|
+
}).execute();
|
|
113
|
+
console.log(`[seed] role_config: ${role}/${configType}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Ensure "human" agent exists
|
|
119
|
+
const humanExists = await db
|
|
120
|
+
.selectFrom("agents")
|
|
121
|
+
.where("name", "=", "human")
|
|
122
|
+
.selectAll()
|
|
123
|
+
.executeTakeFirst();
|
|
124
|
+
|
|
125
|
+
if (!humanExists) {
|
|
126
|
+
await db.insertInto("agents").values({
|
|
127
|
+
id: crypto.randomUUID(),
|
|
128
|
+
name: "human",
|
|
129
|
+
role: "human",
|
|
130
|
+
status: "active",
|
|
131
|
+
current_task: null,
|
|
132
|
+
session_key: "agent:human:main",
|
|
133
|
+
}).execute();
|
|
134
|
+
console.log("[seed] created human agent");
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Run directly if executed as a script
|
|
139
|
+
if (import.meta.main) {
|
|
140
|
+
await seedDefaults();
|
|
141
|
+
await db.destroy();
|
|
142
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import type { Generated } from "kysely";
|
|
2
|
+
|
|
3
|
+
export interface Database {
|
|
4
|
+
agents: AgentTable;
|
|
5
|
+
tasks: TaskTable;
|
|
6
|
+
task_assignees: TaskAssigneeTable;
|
|
7
|
+
messages: MessageTable;
|
|
8
|
+
activities: ActivityTable;
|
|
9
|
+
notifications: NotificationTable;
|
|
10
|
+
documents: DocumentTable;
|
|
11
|
+
knowledge: KnowledgeTable;
|
|
12
|
+
settings: SettingsTable;
|
|
13
|
+
skills: SkillsTable;
|
|
14
|
+
role_configs: RoleConfigsTable;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface AgentTable {
|
|
18
|
+
id: Generated<string>;
|
|
19
|
+
name: string;
|
|
20
|
+
role: string;
|
|
21
|
+
status: string;
|
|
22
|
+
current_task: string | null;
|
|
23
|
+
location: string | null;
|
|
24
|
+
slack_bot_token: string | null;
|
|
25
|
+
slack_app_token: string | null;
|
|
26
|
+
session_key: string;
|
|
27
|
+
created_at: Generated<string>;
|
|
28
|
+
updated_at: Generated<string>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface TaskTable {
|
|
32
|
+
id: Generated<string>;
|
|
33
|
+
title: string;
|
|
34
|
+
description: string;
|
|
35
|
+
design: string | null;
|
|
36
|
+
acceptance: string | null;
|
|
37
|
+
status: string;
|
|
38
|
+
priority: number;
|
|
39
|
+
task_type: string;
|
|
40
|
+
parent_id: string | null;
|
|
41
|
+
created_by: string;
|
|
42
|
+
created_at: Generated<string>;
|
|
43
|
+
updated_at: Generated<string>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface TaskAssigneeTable {
|
|
47
|
+
task_id: string;
|
|
48
|
+
agent_id: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface MessageTable {
|
|
52
|
+
id: Generated<string>;
|
|
53
|
+
task_id: string;
|
|
54
|
+
from_agent: string;
|
|
55
|
+
content: string;
|
|
56
|
+
created_at: Generated<string>;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface ActivityTable {
|
|
60
|
+
id: Generated<string>;
|
|
61
|
+
type: string;
|
|
62
|
+
agent_id: string;
|
|
63
|
+
task_id: string | null;
|
|
64
|
+
summary: string;
|
|
65
|
+
created_at: Generated<string>;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface NotificationTable {
|
|
69
|
+
id: Generated<string>;
|
|
70
|
+
target_agent: string;
|
|
71
|
+
source_agent: string | null;
|
|
72
|
+
task_id: string | null;
|
|
73
|
+
content: string;
|
|
74
|
+
delivered: Generated<number>;
|
|
75
|
+
created_at: Generated<string>;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface DocumentTable {
|
|
79
|
+
id: Generated<string>;
|
|
80
|
+
title: string;
|
|
81
|
+
content: string;
|
|
82
|
+
doc_type: string;
|
|
83
|
+
task_id: string | null;
|
|
84
|
+
created_by: string;
|
|
85
|
+
created_at: Generated<string>;
|
|
86
|
+
updated_at: Generated<string>;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface KnowledgeTable {
|
|
90
|
+
id: Generated<string>;
|
|
91
|
+
key: string;
|
|
92
|
+
content: string;
|
|
93
|
+
source: string;
|
|
94
|
+
task_id: string | null;
|
|
95
|
+
tags: string;
|
|
96
|
+
created_at: Generated<string>;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export interface SettingsTable {
|
|
100
|
+
key: string;
|
|
101
|
+
value: string;
|
|
102
|
+
category: string;
|
|
103
|
+
description: string | null;
|
|
104
|
+
sensitive: number;
|
|
105
|
+
input_type: string;
|
|
106
|
+
updated_at: Generated<string>;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface SkillsTable {
|
|
110
|
+
id: string;
|
|
111
|
+
name: string;
|
|
112
|
+
body: string;
|
|
113
|
+
category: string;
|
|
114
|
+
tags: string;
|
|
115
|
+
created_at: Generated<string>;
|
|
116
|
+
updated_at: Generated<string>;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export interface RoleConfigsTable {
|
|
120
|
+
id: string;
|
|
121
|
+
role: string;
|
|
122
|
+
config_type: string;
|
|
123
|
+
content: string;
|
|
124
|
+
created_at: Generated<string>;
|
|
125
|
+
updated_at: Generated<string>;
|
|
126
|
+
}
|
package/src/api/index.ts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import { cors } from "hono/cors";
|
|
3
|
+
import { serveStatic } from "hono/bun";
|
|
4
|
+
import * as path from "path";
|
|
5
|
+
import { reconcileDbFromFleet, startWatcher } from "./lib/fleet-sync.js";
|
|
6
|
+
import { agents } from "./routes/agents.js";
|
|
7
|
+
import { tasks } from "./routes/tasks.js";
|
|
8
|
+
import { messages } from "./routes/messages.js";
|
|
9
|
+
import { notifications } from "./routes/notifications.js";
|
|
10
|
+
import { activities } from "./routes/activities.js";
|
|
11
|
+
import { documents } from "./routes/documents.js";
|
|
12
|
+
import { knowledge } from "./routes/knowledge.js";
|
|
13
|
+
import { settings } from "./routes/settings.js";
|
|
14
|
+
import { oauth } from "./routes/oauth.js";
|
|
15
|
+
import { skills } from "./routes/skills.js";
|
|
16
|
+
import { roleConfigs } from "./routes/role-configs.js";
|
|
17
|
+
|
|
18
|
+
const app = new Hono();
|
|
19
|
+
|
|
20
|
+
// CORS — dashboard on :3001 hits API on :3100
|
|
21
|
+
app.use("*", cors());
|
|
22
|
+
|
|
23
|
+
// Global error handler — return 400/500 JSON instead of crashing
|
|
24
|
+
app.onError((err, c) => {
|
|
25
|
+
const msg = err.message ?? "internal error";
|
|
26
|
+
const errMsg = String((err as any).message ?? "");
|
|
27
|
+
if (errMsg.includes("UNIQUE constraint") || errMsg.includes("FOREIGN KEY constraint") || errMsg.includes("NOT NULL constraint") || errMsg.includes("CHECK constraint")) {
|
|
28
|
+
return c.json({ error: msg }, 400);
|
|
29
|
+
}
|
|
30
|
+
console.error(err);
|
|
31
|
+
return c.json({ error: msg }, 500);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
app.get("/health", (c) => c.json({ status: "ok" }));
|
|
35
|
+
app.route("/agents", agents);
|
|
36
|
+
app.route("/tasks", tasks);
|
|
37
|
+
app.route("/tasks", messages);
|
|
38
|
+
app.route("/notifications", notifications);
|
|
39
|
+
app.route("/activities", activities);
|
|
40
|
+
app.route("/documents", documents);
|
|
41
|
+
app.route("/knowledge", knowledge);
|
|
42
|
+
app.route("/settings", settings);
|
|
43
|
+
app.route("/oauth", oauth);
|
|
44
|
+
app.route("/skills", skills);
|
|
45
|
+
app.route("/role-configs", roleConfigs);
|
|
46
|
+
|
|
47
|
+
// Serve the dashboard static export
|
|
48
|
+
const dashboardPaths = [
|
|
49
|
+
path.resolve(import.meta.dir, "../../dashboard/out"),
|
|
50
|
+
path.resolve(process.cwd(), "dashboard/out"),
|
|
51
|
+
];
|
|
52
|
+
const dashboardDir = dashboardPaths.find((p) => {
|
|
53
|
+
try { return Bun.file(path.join(p, "index.html")).size > 0; } catch { return false; }
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
if (dashboardDir) {
|
|
57
|
+
app.use("/assets/*", serveStatic({ root: dashboardDir }));
|
|
58
|
+
app.use("/_next/*", serveStatic({ root: dashboardDir }));
|
|
59
|
+
app.get("/", serveStatic({ root: dashboardDir, path: "/index.html" }));
|
|
60
|
+
// Fallback: serve index.html for any non-API route (SPA routing)
|
|
61
|
+
app.use("*", serveStatic({ root: dashboardDir, rewriteRequestPath: () => "/index.html" }));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Reconcile fleet.json → DB on startup, then watch for changes
|
|
65
|
+
reconcileDbFromFleet()
|
|
66
|
+
.then(() => startWatcher())
|
|
67
|
+
.catch((err) => console.error("[startup] fleet sync failed:", err));
|
|
68
|
+
|
|
69
|
+
export default {
|
|
70
|
+
port: Number(process.env.PORT ?? 3100),
|
|
71
|
+
hostname: "0.0.0.0",
|
|
72
|
+
fetch: app.fetch,
|
|
73
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { db } from "../db/client.js";
|
|
2
|
+
|
|
3
|
+
export async function logActivity(
|
|
4
|
+
type: string,
|
|
5
|
+
agentId: string,
|
|
6
|
+
summary: string,
|
|
7
|
+
taskId?: string | null
|
|
8
|
+
) {
|
|
9
|
+
await db.insertInto("activities").values({
|
|
10
|
+
id: crypto.randomUUID(),
|
|
11
|
+
type, agent_id: agentId, task_id: taskId ?? null, summary,
|
|
12
|
+
}).execute();
|
|
13
|
+
}
|