kitty-hive 0.1.0 → 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 +106 -129
- package/README.zh.md +136 -105
- package/channel.ts +194 -431
- package/dist/auth.d.ts +10 -6
- package/dist/auth.js +32 -17
- package/dist/auth.js.map +1 -1
- package/dist/db.d.ts +32 -16
- package/dist/db.js +210 -113
- package/dist/db.js.map +1 -1
- package/dist/federation-http.d.ts +2 -0
- package/dist/federation-http.js +233 -0
- package/dist/federation-http.js.map +1 -0
- package/dist/index.js +82 -79
- package/dist/index.js.map +1 -1
- package/dist/log.d.ts +3 -0
- package/dist/log.js +10 -0
- package/dist/log.js.map +1 -0
- package/dist/mcp/agent-tools.d.ts +2 -0
- package/dist/mcp/agent-tools.js +66 -0
- package/dist/mcp/agent-tools.js.map +1 -0
- package/dist/mcp/dm-tools.d.ts +2 -0
- package/dist/mcp/dm-tools.js +54 -0
- package/dist/mcp/dm-tools.js.map +1 -0
- package/dist/mcp/federation-tools.d.ts +2 -0
- package/dist/mcp/federation-tools.js +30 -0
- package/dist/mcp/federation-tools.js.map +1 -0
- package/dist/mcp/server.d.ts +2 -0
- package/dist/mcp/server.js +62 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/task-tools.d.ts +2 -0
- package/dist/mcp/task-tools.js +128 -0
- package/dist/mcp/task-tools.js.map +1 -0
- package/dist/mcp/team-tools.d.ts +2 -0
- package/dist/mcp/team-tools.js +95 -0
- package/dist/mcp/team-tools.js.map +1 -0
- package/dist/models.d.ts +22 -10
- package/dist/models.js +1 -3
- package/dist/models.js.map +1 -1
- package/dist/server.d.ts +2 -3
- package/dist/server.js +38 -761
- package/dist/server.js.map +1 -1
- package/dist/sessions.d.ts +15 -0
- package/dist/sessions.js +107 -0
- package/dist/sessions.js.map +1 -0
- package/dist/tools/dm.d.ts +3 -2
- package/dist/tools/dm.js +14 -29
- package/dist/tools/dm.js.map +1 -1
- package/dist/tools/start.d.ts +3 -3
- package/dist/tools/start.js +18 -15
- package/dist/tools/start.js.map +1 -1
- package/dist/tools/task.d.ts +1 -1
- package/dist/tools/task.js +6 -5
- package/dist/tools/task.js.map +1 -1
- package/dist/tools/team.d.ts +62 -13
- package/dist/tools/team.js +102 -30
- package/dist/tools/team.js.map +1 -1
- package/package.json +2 -2
- package/dist/tools/room.d.ts +0 -39
- package/dist/tools/room.js +0 -35
- package/dist/tools/room.js.map +0 -1
package/dist/auth.d.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
1
2
|
import type { Agent } from './models.js';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
export declare const asParam: z.ZodOptional<z.ZodString>;
|
|
4
|
+
export declare function resolveAgent(extra: any, asValue?: string): Agent | null;
|
|
5
|
+
export declare function authError(): {
|
|
6
|
+
content: {
|
|
7
|
+
type: "text";
|
|
8
|
+
text: string;
|
|
9
|
+
}[];
|
|
10
|
+
isError: boolean;
|
|
11
|
+
};
|
package/dist/auth.js
CHANGED
|
@@ -1,19 +1,34 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
if (
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { getAgentById, touchAgent } from './db.js';
|
|
3
|
+
import { sessionAgents } from './sessions.js';
|
|
4
|
+
export const asParam = z.string().optional().describe('Your agent id (from hive.start). Optional — session binding takes precedence.');
|
|
5
|
+
export function resolveAgent(extra, asValue) {
|
|
6
|
+
// 1) Session binding (preferred)
|
|
7
|
+
const sessionId = extra?.sessionId;
|
|
8
|
+
if (sessionId) {
|
|
9
|
+
const agentId = sessionAgents.get(sessionId);
|
|
10
|
+
if (agentId) {
|
|
11
|
+
const agent = getAgentById(agentId);
|
|
12
|
+
if (agent) {
|
|
13
|
+
touchAgent(agent.id);
|
|
14
|
+
return agent;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
// 2) `as` param fallback (id only — names not unique)
|
|
19
|
+
if (asValue) {
|
|
20
|
+
const agent = getAgentById(asValue);
|
|
21
|
+
if (agent) {
|
|
22
|
+
touchAgent(agent.id);
|
|
23
|
+
return agent;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
export function authError() {
|
|
29
|
+
return {
|
|
30
|
+
content: [{ type: 'text', text: 'Error: Not authenticated. Call hive.start first.' }],
|
|
31
|
+
isError: true,
|
|
32
|
+
};
|
|
18
33
|
}
|
|
19
34
|
//# sourceMappingURL=auth.js.map
|
package/dist/auth.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAG9C,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,+EAA+E,CAAC,CAAC;AAEvI,MAAM,UAAU,YAAY,CAAC,KAAU,EAAE,OAAgB;IACvD,iCAAiC;IACjC,MAAM,SAAS,GAAG,KAAK,EAAE,SAAS,CAAC;IACnC,IAAI,SAAS,EAAE,CAAC;QACd,MAAM,OAAO,GAAG,aAAa,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAC7C,IAAI,OAAO,EAAE,CAAC;YACZ,MAAM,KAAK,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;YACpC,IAAI,KAAK,EAAE,CAAC;gBAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;gBAAC,OAAO,KAAK,CAAC;YAAC,CAAC;QACpD,CAAC;IACH,CAAC;IACD,sDAAsD;IACtD,IAAI,OAAO,EAAE,CAAC;QACZ,MAAM,KAAK,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;QACpC,IAAI,KAAK,EAAE,CAAC;YAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YAAC,OAAO,KAAK,CAAC;QAAC,CAAC;IACpD,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,SAAS;IACvB,OAAO;QACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,kDAAkD,EAAE,CAAC;QAC9F,OAAO,EAAE,IAAI;KACd,CAAC;AACJ,CAAC"}
|
package/dist/db.d.ts
CHANGED
|
@@ -1,26 +1,42 @@
|
|
|
1
1
|
import Database from 'better-sqlite3';
|
|
2
|
-
import type { Agent,
|
|
2
|
+
import type { Agent, Team, TeamMember, TeamEvent, TeamEventType, DMMessage, Task, TaskEvent, TaskEventType } from './models.js';
|
|
3
3
|
export declare function initDB(dbPath?: string): Database.Database;
|
|
4
4
|
export declare function getDB(): Database.Database;
|
|
5
5
|
export declare function createAgent(displayName: string, tool: string, roles: string, expertise: string): Agent;
|
|
6
6
|
export declare function getAgentByToken(token: string): Agent | undefined;
|
|
7
7
|
export declare function getAgentById(id: string): Agent | undefined;
|
|
8
|
-
export declare function
|
|
8
|
+
export declare function getAgentsByName(name: string): Agent[];
|
|
9
|
+
export declare function listAllAgents(activeOnly?: boolean): Agent[];
|
|
9
10
|
export declare function findAgentByRole(role: string): Agent | undefined;
|
|
10
11
|
export declare function touchAgent(id: string): void;
|
|
11
|
-
export declare function
|
|
12
|
-
export
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
export declare function
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
export declare function
|
|
20
|
-
export declare function
|
|
21
|
-
export declare function
|
|
22
|
-
export declare function
|
|
23
|
-
export declare function
|
|
12
|
+
export declare function renameAgent(id: string, newName: string): void;
|
|
13
|
+
export interface ResolvedAgent {
|
|
14
|
+
agent?: Agent;
|
|
15
|
+
source: 'id' | 'team-nickname' | 'display-name';
|
|
16
|
+
}
|
|
17
|
+
export declare function resolveAddressee(callerAgentId: string, target: string): ResolvedAgent | {
|
|
18
|
+
error: string;
|
|
19
|
+
};
|
|
20
|
+
export declare function createTeam(name: string, hostAgentId: string | null): Team;
|
|
21
|
+
export declare function getTeamById(id: string): Team | undefined;
|
|
22
|
+
export declare function getTeamByName(name: string): Team | undefined;
|
|
23
|
+
export declare function listTeams(activeOnly?: boolean): Team[];
|
|
24
|
+
export declare function getAgentTeams(agentId: string, activeOnly?: boolean): Team[];
|
|
25
|
+
export declare function addTeamMember(teamId: string, agentId: string, nickname?: string | null): TeamMember;
|
|
26
|
+
export declare function removeTeamMember(teamId: string, agentId: string): void;
|
|
27
|
+
export declare function isTeamMember(teamId: string, agentId: string): boolean;
|
|
28
|
+
export declare function getTeamMembers(teamId: string): TeamMember[];
|
|
29
|
+
export declare function getTeamMemberAgentIds(teamId: string): string[];
|
|
30
|
+
export declare function setTeamNickname(teamId: string, agentId: string, nickname: string | null): void;
|
|
31
|
+
export declare function getTeamMember(teamId: string, agentId: string): TeamMember | undefined;
|
|
32
|
+
export declare function getTeamDisplayName(teamId: string, agentId: string): string;
|
|
33
|
+
export declare function appendTeamEvent(teamId: string, type: TeamEventType, actorAgentId: string | null, payload?: object): TeamEvent;
|
|
34
|
+
export declare function getTeamEvents(teamId: string, since?: number, limit?: number): TeamEvent[];
|
|
35
|
+
export declare function getLatestTeamEvents(teamId: string, limit?: number): TeamEvent[];
|
|
36
|
+
export declare function appendDM(fromAgentId: string, toAgentId: string, content: string): DMMessage;
|
|
37
|
+
export declare function getMaxIncomingDMId(toAgentId: string, fromAgentId: string): number;
|
|
38
|
+
export declare function getDMConversation(agentA: string, agentB: string, since?: number, limit?: number): DMMessage[];
|
|
39
|
+
export declare function createTask(title: string, creatorId: string, assigneeId?: string, sourceTeamId?: string, input?: object): Task;
|
|
24
40
|
export declare function getTaskById(id: string): Task | undefined;
|
|
25
41
|
export declare function updateTaskStatus(id: string, status: string, extras?: Record<string, any>): void;
|
|
26
42
|
export declare function getAgentTasks(agentId: string, status?: string): Task[];
|
|
@@ -29,7 +45,7 @@ export declare function getTaskEvents(taskId: string, since?: number, limit?: nu
|
|
|
29
45
|
export declare function getReadCursor(agentId: string, targetType: string, targetId: string): number;
|
|
30
46
|
export declare function setReadCursor(agentId: string, targetType: string, targetId: string, seq: number): void;
|
|
31
47
|
export interface UnreadSummary {
|
|
32
|
-
type: '
|
|
48
|
+
type: 'team' | 'task' | 'dm';
|
|
33
49
|
id: string;
|
|
34
50
|
name: string | null;
|
|
35
51
|
kind: string;
|
package/dist/db.js
CHANGED
|
@@ -28,26 +28,45 @@ export function initDB(dbPath) {
|
|
|
28
28
|
CREATE INDEX IF NOT EXISTS idx_agents_token ON agents(token);
|
|
29
29
|
CREATE INDEX IF NOT EXISTS idx_agents_roles ON agents(roles);
|
|
30
30
|
|
|
31
|
-
CREATE TABLE IF NOT EXISTS
|
|
31
|
+
CREATE TABLE IF NOT EXISTS teams (
|
|
32
32
|
id TEXT PRIMARY KEY,
|
|
33
|
-
name TEXT,
|
|
34
|
-
kind TEXT NOT NULL CHECK(kind IN ('lobby','dm','team')),
|
|
33
|
+
name TEXT NOT NULL UNIQUE,
|
|
35
34
|
host_agent_id TEXT REFERENCES agents(id),
|
|
36
35
|
created_at TEXT NOT NULL,
|
|
37
36
|
closed_at TEXT
|
|
38
37
|
);
|
|
39
|
-
CREATE INDEX IF NOT EXISTS idx_rooms_kind ON rooms(kind);
|
|
40
38
|
|
|
41
|
-
CREATE TABLE IF NOT EXISTS
|
|
39
|
+
CREATE TABLE IF NOT EXISTS team_members (
|
|
40
|
+
team_id TEXT NOT NULL REFERENCES teams(id),
|
|
41
|
+
agent_id TEXT NOT NULL REFERENCES agents(id),
|
|
42
|
+
nickname TEXT,
|
|
43
|
+
joined_at TEXT NOT NULL,
|
|
44
|
+
PRIMARY KEY (team_id, agent_id),
|
|
45
|
+
UNIQUE (team_id, nickname)
|
|
46
|
+
);
|
|
47
|
+
CREATE INDEX IF NOT EXISTS idx_team_members_agent ON team_members(agent_id);
|
|
48
|
+
|
|
49
|
+
CREATE TABLE IF NOT EXISTS team_events (
|
|
42
50
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
43
|
-
|
|
51
|
+
team_id TEXT NOT NULL REFERENCES teams(id),
|
|
44
52
|
seq INTEGER NOT NULL,
|
|
45
|
-
type TEXT NOT NULL CHECK(type IN ('join','leave','message')),
|
|
53
|
+
type TEXT NOT NULL CHECK(type IN ('join','leave','message','rename')),
|
|
46
54
|
actor_agent_id TEXT REFERENCES agents(id),
|
|
47
55
|
payload_json TEXT DEFAULT '{}',
|
|
48
56
|
ts TEXT NOT NULL
|
|
49
57
|
);
|
|
50
|
-
CREATE INDEX IF NOT EXISTS
|
|
58
|
+
CREATE INDEX IF NOT EXISTS idx_team_events_team_seq ON team_events(team_id, seq);
|
|
59
|
+
|
|
60
|
+
CREATE TABLE IF NOT EXISTS dm_messages (
|
|
61
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
62
|
+
seq INTEGER NOT NULL,
|
|
63
|
+
from_agent_id TEXT NOT NULL REFERENCES agents(id),
|
|
64
|
+
to_agent_id TEXT NOT NULL REFERENCES agents(id),
|
|
65
|
+
content TEXT NOT NULL,
|
|
66
|
+
ts TEXT NOT NULL
|
|
67
|
+
);
|
|
68
|
+
CREATE INDEX IF NOT EXISTS idx_dm_to_seq ON dm_messages(to_agent_id, seq);
|
|
69
|
+
CREATE INDEX IF NOT EXISTS idx_dm_from_seq ON dm_messages(from_agent_id, seq);
|
|
51
70
|
|
|
52
71
|
CREATE TABLE IF NOT EXISTS tasks (
|
|
53
72
|
id TEXT PRIMARY KEY,
|
|
@@ -58,7 +77,7 @@ export function initDB(dbPath) {
|
|
|
58
77
|
CHECK(status IN ('created','proposing','approved','in_progress','completed','failed','canceled')),
|
|
59
78
|
workflow_json TEXT,
|
|
60
79
|
current_step INTEGER DEFAULT 0,
|
|
61
|
-
|
|
80
|
+
source_team_id TEXT REFERENCES teams(id),
|
|
62
81
|
input_json TEXT DEFAULT '{}',
|
|
63
82
|
created_at TEXT NOT NULL,
|
|
64
83
|
completed_at TEXT
|
|
@@ -80,7 +99,7 @@ export function initDB(dbPath) {
|
|
|
80
99
|
|
|
81
100
|
CREATE TABLE IF NOT EXISTS read_cursors (
|
|
82
101
|
agent_id TEXT NOT NULL REFERENCES agents(id),
|
|
83
|
-
target_type TEXT NOT NULL CHECK(target_type IN ('
|
|
102
|
+
target_type TEXT NOT NULL CHECK(target_type IN ('team','task','dm')),
|
|
84
103
|
target_id TEXT NOT NULL,
|
|
85
104
|
last_seq INTEGER NOT NULL DEFAULT 0,
|
|
86
105
|
PRIMARY KEY (agent_id, target_type, target_id)
|
|
@@ -123,8 +142,14 @@ export function getAgentByToken(token) {
|
|
|
123
142
|
export function getAgentById(id) {
|
|
124
143
|
return getDB().prepare('SELECT * FROM agents WHERE id = ?').get(id);
|
|
125
144
|
}
|
|
126
|
-
export function
|
|
127
|
-
return getDB().prepare('SELECT * FROM agents WHERE display_name = ?').
|
|
145
|
+
export function getAgentsByName(name) {
|
|
146
|
+
return getDB().prepare('SELECT * FROM agents WHERE display_name = ?').all(name);
|
|
147
|
+
}
|
|
148
|
+
export function listAllAgents(activeOnly = false) {
|
|
149
|
+
const sql = activeOnly
|
|
150
|
+
? "SELECT * FROM agents WHERE status = 'active' ORDER BY last_seen DESC"
|
|
151
|
+
: 'SELECT * FROM agents ORDER BY last_seen DESC';
|
|
152
|
+
return getDB().prepare(sql).all();
|
|
128
153
|
}
|
|
129
154
|
export function findAgentByRole(role) {
|
|
130
155
|
return getDB().prepare("SELECT * FROM agents WHERE roles LIKE ? AND status = 'active' ORDER BY last_seen DESC LIMIT 1").get(`%${role}%`);
|
|
@@ -132,106 +157,159 @@ export function findAgentByRole(role) {
|
|
|
132
157
|
export function touchAgent(id) {
|
|
133
158
|
getDB().prepare('UPDATE agents SET last_seen = ? WHERE id = ?').run(nowISO(), id);
|
|
134
159
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
160
|
+
export function renameAgent(id, newName) {
|
|
161
|
+
getDB().prepare('UPDATE agents SET display_name = ? WHERE id = ?').run(newName, id);
|
|
162
|
+
}
|
|
163
|
+
export function resolveAddressee(callerAgentId, target) {
|
|
164
|
+
// 1) direct id
|
|
165
|
+
const byId = getAgentById(target);
|
|
166
|
+
if (byId)
|
|
167
|
+
return { agent: byId, source: 'id' };
|
|
168
|
+
// 2) nickname in caller's teams
|
|
169
|
+
const nickMatches = getDB().prepare(`
|
|
170
|
+
SELECT DISTINCT a.* FROM agents a
|
|
171
|
+
JOIN team_members tm ON tm.agent_id = a.id
|
|
172
|
+
WHERE tm.nickname = ?
|
|
173
|
+
AND tm.team_id IN (SELECT team_id FROM team_members WHERE agent_id = ?)
|
|
174
|
+
`).all(target, callerAgentId);
|
|
175
|
+
if (nickMatches.length === 1)
|
|
176
|
+
return { agent: nickMatches[0], source: 'team-nickname' };
|
|
177
|
+
if (nickMatches.length > 1)
|
|
178
|
+
return { error: `Nickname "${target}" matches multiple agents in your teams. Use agent id instead.` };
|
|
179
|
+
// 3) global display_name
|
|
180
|
+
const nameMatches = getAgentsByName(target);
|
|
181
|
+
if (nameMatches.length === 1)
|
|
182
|
+
return { agent: nameMatches[0], source: 'display-name' };
|
|
183
|
+
if (nameMatches.length > 1)
|
|
184
|
+
return { error: `Display name "${target}" matches multiple agents. Use agent id instead.` };
|
|
185
|
+
return { error: `No agent found matching "${target}".` };
|
|
186
|
+
}
|
|
187
|
+
// --- Team queries ---
|
|
188
|
+
export function createTeam(name, hostAgentId) {
|
|
189
|
+
const team = {
|
|
190
|
+
id: ulid(), name,
|
|
191
|
+
host_agent_id: hostAgentId,
|
|
192
|
+
created_at: nowISO(), closed_at: null,
|
|
140
193
|
};
|
|
141
194
|
getDB().prepare(`
|
|
142
|
-
INSERT INTO
|
|
143
|
-
VALUES (@id, @name, @
|
|
144
|
-
`).run(
|
|
145
|
-
return
|
|
195
|
+
INSERT INTO teams (id, name, host_agent_id, created_at, closed_at)
|
|
196
|
+
VALUES (@id, @name, @host_agent_id, @created_at, @closed_at)
|
|
197
|
+
`).run(team);
|
|
198
|
+
return team;
|
|
199
|
+
}
|
|
200
|
+
export function getTeamById(id) {
|
|
201
|
+
return getDB().prepare('SELECT * FROM teams WHERE id = ?').get(id);
|
|
202
|
+
}
|
|
203
|
+
export function getTeamByName(name) {
|
|
204
|
+
return getDB().prepare('SELECT * FROM teams WHERE name = ?').get(name);
|
|
205
|
+
}
|
|
206
|
+
export function listTeams(activeOnly = true) {
|
|
207
|
+
const sql = activeOnly
|
|
208
|
+
? 'SELECT * FROM teams WHERE closed_at IS NULL ORDER BY created_at DESC'
|
|
209
|
+
: 'SELECT * FROM teams ORDER BY created_at DESC';
|
|
210
|
+
return getDB().prepare(sql).all();
|
|
211
|
+
}
|
|
212
|
+
export function getAgentTeams(agentId, activeOnly = true) {
|
|
213
|
+
const sql = activeOnly
|
|
214
|
+
? `SELECT t.* FROM teams t JOIN team_members tm ON tm.team_id = t.id WHERE tm.agent_id = ? AND t.closed_at IS NULL ORDER BY tm.joined_at DESC`
|
|
215
|
+
: `SELECT t.* FROM teams t JOIN team_members tm ON tm.team_id = t.id WHERE tm.agent_id = ? ORDER BY tm.joined_at DESC`;
|
|
216
|
+
return getDB().prepare(sql).all(agentId);
|
|
217
|
+
}
|
|
218
|
+
// --- Team membership ---
|
|
219
|
+
export function addTeamMember(teamId, agentId, nickname = null) {
|
|
220
|
+
const member = { team_id: teamId, agent_id: agentId, nickname, joined_at: nowISO() };
|
|
221
|
+
getDB().prepare(`
|
|
222
|
+
INSERT INTO team_members (team_id, agent_id, nickname, joined_at)
|
|
223
|
+
VALUES (@team_id, @agent_id, @nickname, @joined_at)
|
|
224
|
+
ON CONFLICT(team_id, agent_id) DO NOTHING
|
|
225
|
+
`).run(member);
|
|
226
|
+
return member;
|
|
146
227
|
}
|
|
147
|
-
export function
|
|
148
|
-
|
|
228
|
+
export function removeTeamMember(teamId, agentId) {
|
|
229
|
+
getDB().prepare('DELETE FROM team_members WHERE team_id = ? AND agent_id = ?').run(teamId, agentId);
|
|
149
230
|
}
|
|
150
|
-
export function
|
|
151
|
-
|
|
231
|
+
export function isTeamMember(teamId, agentId) {
|
|
232
|
+
const row = getDB().prepare('SELECT 1 FROM team_members WHERE team_id = ? AND agent_id = ?').get(teamId, agentId);
|
|
233
|
+
return !!row;
|
|
152
234
|
}
|
|
153
|
-
export function
|
|
154
|
-
return getDB().prepare(
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
}
|
|
162
|
-
export function
|
|
163
|
-
return getDB().prepare(
|
|
164
|
-
}
|
|
165
|
-
//
|
|
166
|
-
export function
|
|
235
|
+
export function getTeamMembers(teamId) {
|
|
236
|
+
return getDB().prepare('SELECT * FROM team_members WHERE team_id = ?').all(teamId);
|
|
237
|
+
}
|
|
238
|
+
export function getTeamMemberAgentIds(teamId) {
|
|
239
|
+
return getDB().prepare('SELECT agent_id FROM team_members WHERE team_id = ?').all(teamId).map(r => r.agent_id);
|
|
240
|
+
}
|
|
241
|
+
export function setTeamNickname(teamId, agentId, nickname) {
|
|
242
|
+
getDB().prepare('UPDATE team_members SET nickname = ? WHERE team_id = ? AND agent_id = ?').run(nickname, teamId, agentId);
|
|
243
|
+
}
|
|
244
|
+
export function getTeamMember(teamId, agentId) {
|
|
245
|
+
return getDB().prepare('SELECT * FROM team_members WHERE team_id = ? AND agent_id = ?').get(teamId, agentId);
|
|
246
|
+
}
|
|
247
|
+
// Display name for an agent in a team context (nickname || display_name)
|
|
248
|
+
export function getTeamDisplayName(teamId, agentId) {
|
|
249
|
+
const member = getTeamMember(teamId, agentId);
|
|
250
|
+
if (member?.nickname)
|
|
251
|
+
return member.nickname;
|
|
252
|
+
const agent = getAgentById(agentId);
|
|
253
|
+
return agent?.display_name ?? 'unknown';
|
|
254
|
+
}
|
|
255
|
+
// --- Team event queries ---
|
|
256
|
+
export function appendTeamEvent(teamId, type, actorAgentId, payload = {}) {
|
|
167
257
|
const d = getDB();
|
|
168
258
|
const ts = nowISO();
|
|
169
259
|
const payloadJson = JSON.stringify(payload);
|
|
170
260
|
const insert = d.transaction(() => {
|
|
171
|
-
const maxSeq = d.prepare('SELECT COALESCE(MAX(seq), 0) as max_seq FROM
|
|
261
|
+
const maxSeq = d.prepare('SELECT COALESCE(MAX(seq), 0) as max_seq FROM team_events WHERE team_id = ?').get(teamId);
|
|
172
262
|
const seq = maxSeq.max_seq + 1;
|
|
173
|
-
const result = d.prepare('INSERT INTO
|
|
263
|
+
const result = d.prepare('INSERT INTO team_events (team_id, seq, type, actor_agent_id, payload_json, ts) VALUES (?, ?, ?, ?, ?, ?)').run(teamId, seq, type, actorAgentId, payloadJson, ts);
|
|
174
264
|
return { id: result.lastInsertRowid, seq };
|
|
175
265
|
});
|
|
176
266
|
const { id, seq } = insert();
|
|
177
|
-
return { id,
|
|
178
|
-
}
|
|
179
|
-
export function
|
|
180
|
-
return getDB().prepare('SELECT * FROM
|
|
181
|
-
}
|
|
182
|
-
export function
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
export function
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
WHERE e2.room_id = r.id AND e2.type = 'leave' AND e2.actor_agent_id = ? AND e2.seq > e.seq
|
|
210
|
-
)
|
|
211
|
-
`;
|
|
212
|
-
const params = [agentId, agentId];
|
|
213
|
-
if (kind) {
|
|
214
|
-
sql += ' AND r.kind = ?';
|
|
215
|
-
params.push(kind);
|
|
216
|
-
}
|
|
217
|
-
if (activeOnly) {
|
|
218
|
-
sql += ' AND r.closed_at IS NULL';
|
|
219
|
-
}
|
|
220
|
-
return getDB().prepare(sql).all(...params);
|
|
267
|
+
return { id, team_id: teamId, seq, type, actor_agent_id: actorAgentId, payload_json: payloadJson, ts };
|
|
268
|
+
}
|
|
269
|
+
export function getTeamEvents(teamId, since = 0, limit = 50) {
|
|
270
|
+
return getDB().prepare('SELECT * FROM team_events WHERE team_id = ? AND seq > ? ORDER BY seq ASC LIMIT ?').all(teamId, since, limit);
|
|
271
|
+
}
|
|
272
|
+
export function getLatestTeamEvents(teamId, limit = 10) {
|
|
273
|
+
return getDB().prepare('SELECT * FROM (SELECT * FROM team_events WHERE team_id = ? ORDER BY seq DESC LIMIT ?) ORDER BY seq ASC').all(teamId, limit);
|
|
274
|
+
}
|
|
275
|
+
// --- DM queries ---
|
|
276
|
+
export function appendDM(fromAgentId, toAgentId, content) {
|
|
277
|
+
const d = getDB();
|
|
278
|
+
const ts = nowISO();
|
|
279
|
+
const insert = d.transaction(() => {
|
|
280
|
+
const maxSeq = d.prepare('SELECT COALESCE(MAX(seq), 0) as max_seq FROM dm_messages WHERE to_agent_id = ?').get(toAgentId);
|
|
281
|
+
const seq = maxSeq.max_seq + 1;
|
|
282
|
+
const result = d.prepare('INSERT INTO dm_messages (seq, from_agent_id, to_agent_id, content, ts) VALUES (?, ?, ?, ?, ?)').run(seq, fromAgentId, toAgentId, content, ts);
|
|
283
|
+
return { id: result.lastInsertRowid, seq };
|
|
284
|
+
});
|
|
285
|
+
const { id, seq } = insert();
|
|
286
|
+
return { id, seq, from_agent_id: fromAgentId, to_agent_id: toAgentId, content, ts };
|
|
287
|
+
}
|
|
288
|
+
export function getMaxIncomingDMId(toAgentId, fromAgentId) {
|
|
289
|
+
const row = getDB().prepare('SELECT MAX(id) AS max_id FROM dm_messages WHERE to_agent_id = ? AND from_agent_id = ?').get(toAgentId, fromAgentId);
|
|
290
|
+
return row?.max_id ?? 0;
|
|
291
|
+
}
|
|
292
|
+
export function getDMConversation(agentA, agentB, since = 0, limit = 50) {
|
|
293
|
+
return getDB().prepare(`
|
|
294
|
+
SELECT * FROM dm_messages
|
|
295
|
+
WHERE ((from_agent_id = ? AND to_agent_id = ?) OR (from_agent_id = ? AND to_agent_id = ?))
|
|
296
|
+
AND id > ?
|
|
297
|
+
ORDER BY id ASC LIMIT ?
|
|
298
|
+
`).all(agentA, agentB, agentB, agentA, since, limit);
|
|
221
299
|
}
|
|
222
300
|
// --- Task queries ---
|
|
223
|
-
export function createTask(title, creatorId, assigneeId,
|
|
301
|
+
export function createTask(title, creatorId, assigneeId, sourceTeamId, input) {
|
|
224
302
|
const task = {
|
|
225
303
|
id: ulid(), title, creator_agent_id: creatorId,
|
|
226
304
|
assignee_agent_id: assigneeId ?? null,
|
|
227
305
|
status: 'created', workflow_json: null, current_step: 0,
|
|
228
|
-
|
|
306
|
+
source_team_id: sourceTeamId ?? null,
|
|
229
307
|
input_json: JSON.stringify(input ?? {}),
|
|
230
308
|
created_at: nowISO(), completed_at: null,
|
|
231
309
|
};
|
|
232
310
|
getDB().prepare(`
|
|
233
|
-
INSERT INTO tasks (id, title, creator_agent_id, assignee_agent_id, status, workflow_json, current_step,
|
|
234
|
-
VALUES (@id, @title, @creator_agent_id, @assignee_agent_id, @status, @workflow_json, @current_step, @
|
|
311
|
+
INSERT INTO tasks (id, title, creator_agent_id, assignee_agent_id, status, workflow_json, current_step, source_team_id, input_json, created_at, completed_at)
|
|
312
|
+
VALUES (@id, @title, @creator_agent_id, @assignee_agent_id, @status, @workflow_json, @current_step, @source_team_id, @input_json, @created_at, @completed_at)
|
|
235
313
|
`).run(task);
|
|
236
314
|
return task;
|
|
237
315
|
}
|
|
@@ -291,29 +369,38 @@ export function setReadCursor(agentId, targetType, targetId, seq) {
|
|
|
291
369
|
}
|
|
292
370
|
export function getUnreadForAgent(agentId) {
|
|
293
371
|
const result = [];
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
const
|
|
372
|
+
const d = getDB();
|
|
373
|
+
// Team unread
|
|
374
|
+
const teams = getAgentTeams(agentId, true);
|
|
375
|
+
for (const team of teams) {
|
|
376
|
+
const cursor = getReadCursor(agentId, 'team', team.id);
|
|
377
|
+
const unread = d.prepare('SELECT * FROM team_events WHERE team_id = ? AND seq > ? AND actor_agent_id != ? ORDER BY seq ASC LIMIT 10').all(team.id, cursor, agentId);
|
|
299
378
|
if (unread.length === 0)
|
|
300
379
|
continue;
|
|
301
|
-
const totalUnread =
|
|
302
|
-
const latest = unread.slice(-5).map(e => {
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
380
|
+
const totalUnread = d.prepare('SELECT COUNT(*) as cnt FROM team_events WHERE team_id = ? AND seq > ? AND actor_agent_id != ?').get(team.id, cursor, agentId).cnt;
|
|
381
|
+
const latest = unread.slice(-5).map(e => ({
|
|
382
|
+
from: e.actor_agent_id ? getTeamDisplayName(team.id, e.actor_agent_id) : 'system',
|
|
383
|
+
type: e.type,
|
|
384
|
+
preview: previewFromPayload(e.payload_json),
|
|
385
|
+
ts: e.ts,
|
|
386
|
+
}));
|
|
387
|
+
result.push({ type: 'team', id: team.id, name: team.name, kind: 'team', unread_count: totalUnread, latest });
|
|
388
|
+
}
|
|
389
|
+
// DM unread (per-sender cursor: target_id = sender_agent_id)
|
|
390
|
+
const senders = d.prepare('SELECT DISTINCT from_agent_id FROM dm_messages WHERE to_agent_id = ?').all(agentId);
|
|
391
|
+
for (const { from_agent_id: senderId } of senders) {
|
|
392
|
+
const cursor = getReadCursor(agentId, 'dm', senderId);
|
|
393
|
+
const msgs = d.prepare('SELECT * FROM dm_messages WHERE to_agent_id = ? AND from_agent_id = ? AND id > ? ORDER BY id ASC').all(agentId, senderId, cursor);
|
|
394
|
+
if (msgs.length === 0)
|
|
395
|
+
continue;
|
|
396
|
+
const sender = getAgentById(senderId);
|
|
397
|
+
const latest = msgs.slice(-5).map(m => ({
|
|
398
|
+
from: sender?.display_name ?? 'unknown',
|
|
399
|
+
type: 'dm',
|
|
400
|
+
preview: m.content.length > 200 ? m.content.slice(0, 200) + ' [summary]' : m.content,
|
|
401
|
+
ts: m.ts,
|
|
402
|
+
}));
|
|
403
|
+
result.push({ type: 'dm', id: senderId, name: sender?.display_name ?? null, kind: 'dm', unread_count: msgs.length, latest });
|
|
317
404
|
}
|
|
318
405
|
// Task unread
|
|
319
406
|
const tasks = getAgentTasks(agentId);
|
|
@@ -321,12 +408,12 @@ export function getUnreadForAgent(agentId) {
|
|
|
321
408
|
if (['completed', 'failed', 'canceled'].includes(task.status))
|
|
322
409
|
continue;
|
|
323
410
|
const cursor = getReadCursor(agentId, 'task', task.id);
|
|
324
|
-
const unread =
|
|
411
|
+
const unread = d.prepare('SELECT * FROM task_events WHERE task_id = ? AND seq > ? AND actor_agent_id != ? ORDER BY seq ASC LIMIT 10').all(task.id, cursor, agentId);
|
|
325
412
|
if (unread.length === 0)
|
|
326
413
|
continue;
|
|
327
|
-
const totalUnread =
|
|
414
|
+
const totalUnread = d.prepare('SELECT COUNT(*) as cnt FROM task_events WHERE task_id = ? AND seq > ? AND actor_agent_id != ?').get(task.id, cursor, agentId).cnt;
|
|
328
415
|
const latest = unread.slice(-5).map(e => {
|
|
329
|
-
const actor = getAgentById(e.actor_agent_id
|
|
416
|
+
const actor = e.actor_agent_id ? getAgentById(e.actor_agent_id) : null;
|
|
330
417
|
let preview = '';
|
|
331
418
|
try {
|
|
332
419
|
const p = JSON.parse(e.payload_json);
|
|
@@ -343,6 +430,16 @@ export function getUnreadForAgent(agentId) {
|
|
|
343
430
|
}
|
|
344
431
|
return result;
|
|
345
432
|
}
|
|
433
|
+
function previewFromPayload(payloadJson) {
|
|
434
|
+
try {
|
|
435
|
+
const p = JSON.parse(payloadJson);
|
|
436
|
+
const text = p.content || p.message || p.preview || '';
|
|
437
|
+
return text.length > 200 ? text.slice(0, 200) + ' [summary]' : text;
|
|
438
|
+
}
|
|
439
|
+
catch {
|
|
440
|
+
return '';
|
|
441
|
+
}
|
|
442
|
+
}
|
|
346
443
|
export function addPeer(name, url, secret, exposed = '') {
|
|
347
444
|
const peer = {
|
|
348
445
|
id: ulid(), name, url, secret, exposed, status: 'active',
|