@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/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;