@defai.digital/session-domain 13.0.3
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/LICENSE +214 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/manager.d.ts +99 -0
- package/dist/manager.d.ts.map +1 -0
- package/dist/manager.js +609 -0
- package/dist/manager.js.map +1 -0
- package/dist/store.d.ts +67 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +188 -0
- package/dist/store.js.map +1 -0
- package/dist/types.d.ts +155 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +16 -0
- package/dist/types.js.map +1 -0
- package/package.json +48 -0
- package/src/index.ts +75 -0
- package/src/manager.ts +834 -0
- package/src/store.ts +240 -0
- package/src/types.ts +214 -0
package/src/store.ts
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Store Implementation
|
|
3
|
+
*
|
|
4
|
+
* Provides storage for session state.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type {
|
|
8
|
+
Session,
|
|
9
|
+
CreateSessionInput,
|
|
10
|
+
} from '@defai.digital/contracts';
|
|
11
|
+
import type { SessionStore, SessionFilter } from './types.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* In-memory session store implementation
|
|
15
|
+
*/
|
|
16
|
+
export class InMemorySessionStore implements SessionStore {
|
|
17
|
+
private readonly sessions = new Map<string, Session>();
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Create a new session
|
|
21
|
+
*/
|
|
22
|
+
async create(input: CreateSessionInput): Promise<Session> {
|
|
23
|
+
const now = new Date().toISOString();
|
|
24
|
+
const sessionId = crypto.randomUUID();
|
|
25
|
+
|
|
26
|
+
const session: Session = {
|
|
27
|
+
sessionId,
|
|
28
|
+
initiator: input.initiator,
|
|
29
|
+
task: input.task,
|
|
30
|
+
participants: [
|
|
31
|
+
{
|
|
32
|
+
agentId: input.initiator,
|
|
33
|
+
role: 'initiator',
|
|
34
|
+
joinedAt: now,
|
|
35
|
+
tasks: [],
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
status: 'active',
|
|
39
|
+
createdAt: now,
|
|
40
|
+
updatedAt: now,
|
|
41
|
+
version: 1,
|
|
42
|
+
workspace: input.workspace,
|
|
43
|
+
metadata: input.metadata,
|
|
44
|
+
appliedPolicies: [],
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
this.sessions.set(sessionId, session);
|
|
48
|
+
return session;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Get a session by ID
|
|
53
|
+
*/
|
|
54
|
+
async get(sessionId: string): Promise<Session | undefined> {
|
|
55
|
+
return this.sessions.get(sessionId);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Update a session
|
|
60
|
+
*/
|
|
61
|
+
async update(sessionId: string, session: Session): Promise<void> {
|
|
62
|
+
if (!this.sessions.has(sessionId)) {
|
|
63
|
+
throw new Error(`Session ${sessionId} not found`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Check version for optimistic concurrency
|
|
67
|
+
const existing = this.sessions.get(sessionId)!;
|
|
68
|
+
if (existing.version !== session.version - 1) {
|
|
69
|
+
throw new SessionVersionConflictError(sessionId, existing.version, session.version);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
this.sessions.set(sessionId, {
|
|
73
|
+
...session,
|
|
74
|
+
updatedAt: new Date().toISOString(),
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Delete a session
|
|
80
|
+
*/
|
|
81
|
+
async delete(sessionId: string): Promise<void> {
|
|
82
|
+
this.sessions.delete(sessionId);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* List sessions with optional filters
|
|
87
|
+
*/
|
|
88
|
+
async list(filter?: SessionFilter): Promise<Session[]> {
|
|
89
|
+
let sessions = Array.from(this.sessions.values());
|
|
90
|
+
|
|
91
|
+
if (filter !== undefined) {
|
|
92
|
+
if (filter.status !== undefined) {
|
|
93
|
+
sessions = sessions.filter((s) => s.status === filter.status);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (filter.initiator !== undefined) {
|
|
97
|
+
sessions = sessions.filter((s) => s.initiator === filter.initiator);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (filter.participant !== undefined) {
|
|
101
|
+
sessions = sessions.filter((s) =>
|
|
102
|
+
s.participants.some((p) => p.agentId === filter.participant)
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (filter.workspace !== undefined) {
|
|
107
|
+
sessions = sessions.filter((s) => s.workspace === filter.workspace);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (filter.createdAfter !== undefined) {
|
|
111
|
+
const after = new Date(filter.createdAfter).getTime();
|
|
112
|
+
sessions = sessions.filter(
|
|
113
|
+
(s) => new Date(s.createdAt).getTime() > after
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (filter.createdBefore !== undefined) {
|
|
118
|
+
const before = new Date(filter.createdBefore).getTime();
|
|
119
|
+
sessions = sessions.filter(
|
|
120
|
+
(s) => new Date(s.createdAt).getTime() < before
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (filter.limit !== undefined) {
|
|
125
|
+
sessions = sessions.slice(0, filter.limit);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Sort by createdAt descending
|
|
130
|
+
sessions.sort(
|
|
131
|
+
(a, b) =>
|
|
132
|
+
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
return sessions;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Find active session for an agent
|
|
140
|
+
*/
|
|
141
|
+
async findActiveForAgent(
|
|
142
|
+
agentId: string,
|
|
143
|
+
workspace?: string
|
|
144
|
+
): Promise<Session | undefined> {
|
|
145
|
+
for (const session of this.sessions.values()) {
|
|
146
|
+
if (session.status !== 'active') {
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (workspace !== undefined && session.workspace !== workspace) {
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const isParticipant = session.participants.some(
|
|
155
|
+
(p) => p.agentId === agentId && p.leftAt === undefined
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
if (isParticipant) {
|
|
159
|
+
return session;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return undefined;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Apply a governance policy to a session
|
|
168
|
+
*/
|
|
169
|
+
async applyPolicy(sessionId: string, policyId: string): Promise<Session> {
|
|
170
|
+
const session = this.sessions.get(sessionId);
|
|
171
|
+
if (session === undefined) {
|
|
172
|
+
throw new Error(`Session ${sessionId} not found`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Check if policy already applied
|
|
176
|
+
const appliedPolicies = session.appliedPolicies ?? [];
|
|
177
|
+
if (appliedPolicies.includes(policyId)) {
|
|
178
|
+
return session; // Already applied, return as-is
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Apply policy and increment version
|
|
182
|
+
const updatedSession: Session = {
|
|
183
|
+
...session,
|
|
184
|
+
appliedPolicies: [...appliedPolicies, policyId],
|
|
185
|
+
updatedAt: new Date().toISOString(),
|
|
186
|
+
version: session.version + 1,
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
this.sessions.set(sessionId, updatedSession);
|
|
190
|
+
return updatedSession;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Get applied policies for a session
|
|
195
|
+
*/
|
|
196
|
+
async getAppliedPolicies(sessionId: string): Promise<string[]> {
|
|
197
|
+
const session = this.sessions.get(sessionId);
|
|
198
|
+
if (session === undefined) {
|
|
199
|
+
throw new Error(`Session ${sessionId} not found`);
|
|
200
|
+
}
|
|
201
|
+
return session.appliedPolicies ?? [];
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Clear all sessions (useful for testing)
|
|
206
|
+
*/
|
|
207
|
+
clear(): void {
|
|
208
|
+
this.sessions.clear();
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Get the count of sessions
|
|
213
|
+
*/
|
|
214
|
+
get size(): number {
|
|
215
|
+
return this.sessions.size;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Session version conflict error
|
|
221
|
+
*/
|
|
222
|
+
export class SessionVersionConflictError extends Error {
|
|
223
|
+
constructor(
|
|
224
|
+
public readonly sessionId: string,
|
|
225
|
+
public readonly expectedVersion: number,
|
|
226
|
+
public readonly actualVersion: number
|
|
227
|
+
) {
|
|
228
|
+
super(
|
|
229
|
+
`Version conflict for session ${sessionId}: expected ${expectedVersion}, got ${actualVersion}`
|
|
230
|
+
);
|
|
231
|
+
this.name = 'SessionVersionConflictError';
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Creates a new in-memory session store
|
|
237
|
+
*/
|
|
238
|
+
export function createSessionStore(): SessionStore {
|
|
239
|
+
return new InMemorySessionStore();
|
|
240
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Domain Types
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
type Session,
|
|
7
|
+
type SessionTask,
|
|
8
|
+
type SessionStatus,
|
|
9
|
+
type SessionEvent,
|
|
10
|
+
type CreateSessionInput,
|
|
11
|
+
type JoinSessionInput,
|
|
12
|
+
type StartTaskInput,
|
|
13
|
+
type CompleteTaskInput,
|
|
14
|
+
type FailTaskInput,
|
|
15
|
+
// Run history types from CLI contracts
|
|
16
|
+
type RunRecord,
|
|
17
|
+
type HistoryQuery,
|
|
18
|
+
// Constants
|
|
19
|
+
TIMEOUT_SESSION,
|
|
20
|
+
LIMIT_EVENT_BUFFER,
|
|
21
|
+
} from '@defai.digital/contracts';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Session store interface
|
|
25
|
+
*/
|
|
26
|
+
export interface SessionStore {
|
|
27
|
+
/**
|
|
28
|
+
* Create a new session
|
|
29
|
+
*/
|
|
30
|
+
create(input: CreateSessionInput): Promise<Session>;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Get a session by ID
|
|
34
|
+
*/
|
|
35
|
+
get(sessionId: string): Promise<Session | undefined>;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Update a session
|
|
39
|
+
*/
|
|
40
|
+
update(sessionId: string, session: Session): Promise<void>;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Delete a session
|
|
44
|
+
*/
|
|
45
|
+
delete(sessionId: string): Promise<void>;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* List sessions with optional filters
|
|
49
|
+
*/
|
|
50
|
+
list(filter?: SessionFilter): Promise<Session[]>;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Find active session for an agent
|
|
54
|
+
*/
|
|
55
|
+
findActiveForAgent(agentId: string, workspace?: string): Promise<Session | undefined>;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Apply a governance policy to a session
|
|
59
|
+
*/
|
|
60
|
+
applyPolicy(sessionId: string, policyId: string): Promise<Session>;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Get applied policies for a session
|
|
64
|
+
*/
|
|
65
|
+
getAppliedPolicies(sessionId: string): Promise<string[]>;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Filter options for listing sessions
|
|
70
|
+
*/
|
|
71
|
+
export interface SessionFilter {
|
|
72
|
+
status?: SessionStatus;
|
|
73
|
+
initiator?: string;
|
|
74
|
+
participant?: string;
|
|
75
|
+
workspace?: string;
|
|
76
|
+
createdAfter?: string;
|
|
77
|
+
createdBefore?: string;
|
|
78
|
+
limit?: number;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Session manager interface
|
|
83
|
+
*/
|
|
84
|
+
export interface SessionManager {
|
|
85
|
+
/**
|
|
86
|
+
* Create a new session
|
|
87
|
+
*/
|
|
88
|
+
createSession(input: CreateSessionInput): Promise<Session>;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Get a session by ID
|
|
92
|
+
*/
|
|
93
|
+
getSession(sessionId: string): Promise<Session | undefined>;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Join an existing session
|
|
97
|
+
*/
|
|
98
|
+
joinSession(input: JoinSessionInput): Promise<Session>;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Leave a session
|
|
102
|
+
*/
|
|
103
|
+
leaveSession(sessionId: string, agentId: string): Promise<Session>;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Start a task within a session
|
|
107
|
+
*/
|
|
108
|
+
startTask(input: StartTaskInput): Promise<SessionTask>;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Complete a task
|
|
112
|
+
*/
|
|
113
|
+
completeTask(input: CompleteTaskInput): Promise<SessionTask>;
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Fail a task
|
|
117
|
+
*/
|
|
118
|
+
failTask(input: FailTaskInput): Promise<SessionTask>;
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Complete a session
|
|
122
|
+
*/
|
|
123
|
+
completeSession(sessionId: string, summary?: string): Promise<Session>;
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Fail a session
|
|
127
|
+
*/
|
|
128
|
+
failSession(sessionId: string, error: SessionFailure): Promise<Session>;
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* List sessions
|
|
132
|
+
*/
|
|
133
|
+
listSessions(filter?: SessionFilter): Promise<Session[]>;
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Get run history across all sessions
|
|
137
|
+
* Aggregates tasks from all participants across sessions
|
|
138
|
+
*/
|
|
139
|
+
getRunHistory(options?: HistoryQuery): Promise<RunRecord[]>;
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Count active sessions
|
|
143
|
+
*/
|
|
144
|
+
countActiveSessions(): Promise<number>;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Session failure info
|
|
149
|
+
* Matches SessionErrorSchema from contracts
|
|
150
|
+
*/
|
|
151
|
+
export interface SessionFailure {
|
|
152
|
+
code: string;
|
|
153
|
+
message: string;
|
|
154
|
+
taskId?: string;
|
|
155
|
+
retryable?: boolean;
|
|
156
|
+
details?: Record<string, unknown>;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Session event handler
|
|
161
|
+
*/
|
|
162
|
+
export type SessionEventHandler = (event: SessionEvent) => void | Promise<void>;
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Session event emitter interface
|
|
166
|
+
*/
|
|
167
|
+
export interface SessionEventEmitter {
|
|
168
|
+
/**
|
|
169
|
+
* Subscribe to session events
|
|
170
|
+
*/
|
|
171
|
+
on(type: SessionEvent['type'] | '*', handler: SessionEventHandler): void;
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Unsubscribe from session events
|
|
175
|
+
*/
|
|
176
|
+
off(type: SessionEvent['type'] | '*', handler: SessionEventHandler): void;
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Emit a session event
|
|
180
|
+
*/
|
|
181
|
+
emit(event: SessionEvent): void;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Session domain configuration
|
|
186
|
+
*/
|
|
187
|
+
export interface SessionDomainConfig {
|
|
188
|
+
maxParticipants: number;
|
|
189
|
+
maxTasksPerParticipant: number;
|
|
190
|
+
sessionTimeout: number;
|
|
191
|
+
eventBufferSize: number;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Default session domain configuration
|
|
196
|
+
*/
|
|
197
|
+
export const DEFAULT_SESSION_DOMAIN_CONFIG: SessionDomainConfig = {
|
|
198
|
+
maxParticipants: 10,
|
|
199
|
+
maxTasksPerParticipant: 50,
|
|
200
|
+
sessionTimeout: TIMEOUT_SESSION,
|
|
201
|
+
eventBufferSize: LIMIT_EVENT_BUFFER,
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
// ============================================================================
|
|
205
|
+
// Run History Types - Re-exported from contracts for convenience
|
|
206
|
+
// The authoritative definitions are in @defai.digital/contracts (cli/v1)
|
|
207
|
+
// ============================================================================
|
|
208
|
+
|
|
209
|
+
// Re-export contract types to maintain backwards compatibility
|
|
210
|
+
// and provide a convenient import path for session-domain consumers
|
|
211
|
+
export type { RunRecord, RunStatus, HistoryQuery } from '@defai.digital/contracts';
|
|
212
|
+
|
|
213
|
+
// Backwards compatibility alias
|
|
214
|
+
export type RunHistoryOptions = HistoryQuery;
|