@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.
Files changed (85) hide show
  1. package/README.md +272 -0
  2. package/bin/agency.js +2 -0
  3. package/dashboard/out/404.html +1 -0
  4. package/dashboard/out/_next/static/chunks/255-67e8754147461423.js +1 -0
  5. package/dashboard/out/_next/static/chunks/4bd1b696-c023c6e3521b1417.js +1 -0
  6. package/dashboard/out/_next/static/chunks/app/_not-found/page-ad40673d821037f6.js +1 -0
  7. package/dashboard/out/_next/static/chunks/app/layout-056f12675e691d12.js +1 -0
  8. package/dashboard/out/_next/static/chunks/app/page-80f01fdbb09b43c8.js +1 -0
  9. package/dashboard/out/_next/static/chunks/framework-de98b93a850cfc71.js +1 -0
  10. package/dashboard/out/_next/static/chunks/main-1a0dcce460eb61ce.js +1 -0
  11. package/dashboard/out/_next/static/chunks/main-app-1d848b791b823fa6.js +1 -0
  12. package/dashboard/out/_next/static/chunks/pages/_app-7d307437aca18ad4.js +1 -0
  13. package/dashboard/out/_next/static/chunks/pages/_error-cb2a52f75f2162e2.js +1 -0
  14. package/dashboard/out/_next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
  15. package/dashboard/out/_next/static/chunks/webpack-4e6bf084ac60582b.js +1 -0
  16. package/dashboard/out/_next/static/css/27d1ea794f04e96a.css +1 -0
  17. package/dashboard/out/_next/static/pU1nwWH_dNUOCI8y4nl3C/_buildManifest.js +1 -0
  18. package/dashboard/out/_next/static/pU1nwWH_dNUOCI8y4nl3C/_ssgManifest.js +1 -0
  19. package/dashboard/out/index.html +1 -0
  20. package/dashboard/out/index.txt +19 -0
  21. package/docs/images/agency_cli_ps.png +0 -0
  22. package/docs/images/agency_ui_ai_prodivder_settings.png +0 -0
  23. package/docs/images/agency_ui_aws_settings.png +0 -0
  24. package/docs/images/agency_ui_identity_settings.png +0 -0
  25. package/docs/images/agency_ui_mission_control.png +0 -0
  26. package/docs/images/agent_ui_agent_config.png +0 -0
  27. package/package.json +31 -0
  28. package/src/api/db/client.ts +16 -0
  29. package/src/api/db/migrate.ts +37 -0
  30. package/src/api/db/migrations/001_initial.ts +193 -0
  31. package/src/api/db/migrations/002_configs.ts +76 -0
  32. package/src/api/db/migrations/003_settings_columns.ts +13 -0
  33. package/src/api/db/seed.ts +142 -0
  34. package/src/api/db/types.ts +126 -0
  35. package/src/api/index.ts +73 -0
  36. package/src/api/lib/activity.ts +13 -0
  37. package/src/api/lib/fleet-sync.ts +156 -0
  38. package/src/api/lib/mentions.ts +59 -0
  39. package/src/api/lib/processes.ts +45 -0
  40. package/src/api/lib/resolve-agent.ts +5 -0
  41. package/src/api/lib/tunnels.ts +99 -0
  42. package/src/api/routes/activities.ts +27 -0
  43. package/src/api/routes/agents.ts +311 -0
  44. package/src/api/routes/documents.ts +41 -0
  45. package/src/api/routes/knowledge.ts +60 -0
  46. package/src/api/routes/messages.ts +54 -0
  47. package/src/api/routes/notifications.ts +40 -0
  48. package/src/api/routes/oauth.ts +171 -0
  49. package/src/api/routes/role-configs.ts +71 -0
  50. package/src/api/routes/settings.ts +94 -0
  51. package/src/api/routes/skills.ts +76 -0
  52. package/src/api/routes/tasks.ts +154 -0
  53. package/src/cli/commands/config.ts +42 -0
  54. package/src/cli/commands/daemon.ts +173 -0
  55. package/src/cli/commands/doc.ts +47 -0
  56. package/src/cli/commands/init.ts +105 -0
  57. package/src/cli/commands/learn.ts +51 -0
  58. package/src/cli/commands/logs.ts +31 -0
  59. package/src/cli/commands/msg.ts +18 -0
  60. package/src/cli/commands/ps.ts +19 -0
  61. package/src/cli/commands/recall.ts +18 -0
  62. package/src/cli/commands/skills.ts +66 -0
  63. package/src/cli/commands/ssh.ts +68 -0
  64. package/src/cli/commands/start.ts +14 -0
  65. package/src/cli/commands/status.ts +33 -0
  66. package/src/cli/commands/stop.ts +11 -0
  67. package/src/cli/commands/tasks.ts +150 -0
  68. package/src/cli/index.ts +70 -0
  69. package/src/cli/lib/api.ts +16 -0
  70. package/src/cli/lib/config.ts +5 -0
  71. package/src/cli/lib/find-root.ts +32 -0
  72. package/src/cli/lib/prompt.ts +20 -0
  73. package/src/daemon.ts +83 -0
  74. package/src/templates/implementer/agents-config.md +44 -0
  75. package/src/templates/implementer/agents.md +32 -0
  76. package/src/templates/implementer/heartbeat.md +47 -0
  77. package/src/templates/implementer/tools.md +33 -0
  78. package/src/templates/orchestrator/agents-config.md +44 -0
  79. package/src/templates/orchestrator/agents.md +27 -0
  80. package/src/templates/orchestrator/heartbeat.md +40 -0
  81. package/src/templates/orchestrator/tools.md +40 -0
  82. package/src/templates/shared/environment.md +20 -0
  83. package/src/templates/shared/memory.md +20 -0
  84. package/src/templates/shared/soul.md +26 -0
  85. 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
+ }
@@ -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
+ }