human-in-the-loop 0.1.0 → 2.0.2
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/.turbo/turbo-build.log +5 -0
- package/CHANGELOG.md +17 -0
- package/README.md +600 -166
- package/dist/helpers.d.ts +308 -0
- package/dist/helpers.d.ts.map +1 -0
- package/dist/helpers.js +275 -0
- package/dist/helpers.js.map +1 -0
- package/dist/human.d.ts +315 -0
- package/dist/human.d.ts.map +1 -0
- package/dist/human.js +476 -0
- package/dist/human.js.map +1 -0
- package/dist/index.d.ts +48 -343
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +51 -793
- package/dist/index.js.map +1 -0
- package/dist/store.d.ts +61 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +162 -0
- package/dist/store.js.map +1 -0
- package/dist/types.d.ts +399 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/examples/basic-usage.ts +386 -0
- package/package.json +23 -54
- package/src/helpers.ts +379 -0
- package/src/human.test.ts +384 -0
- package/src/human.ts +626 -0
- package/src/index.ts +102 -6
- package/src/store.ts +201 -0
- package/src/types.ts +431 -0
- package/tsconfig.json +6 -11
- package/TODO.md +0 -53
- package/dist/index.cjs +0 -899
- package/dist/index.d.cts +0 -344
- package/src/core/factory.test.ts +0 -69
- package/src/core/factory.ts +0 -30
- package/src/core/types.ts +0 -191
- package/src/platforms/email/index.tsx +0 -137
- package/src/platforms/react/index.tsx +0 -218
- package/src/platforms/slack/index.ts +0 -84
- package/src/platforms/teams/index.ts +0 -84
- package/vitest.config.ts +0 -15
package/src/index.ts
CHANGED
|
@@ -1,7 +1,103 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* human-in-the-loop - Primitives for integrating human oversight and intervention in AI workflows
|
|
3
|
+
*
|
|
4
|
+
* This package provides primitives for human oversight and intervention in AI workflows:
|
|
5
|
+
* - Approval gates and workflows
|
|
6
|
+
* - Review processes and queues
|
|
7
|
+
* - Escalation paths
|
|
8
|
+
* - Human intervention points
|
|
9
|
+
* - Role and team management
|
|
10
|
+
* - Goals, KPIs, and OKRs tracking
|
|
11
|
+
*
|
|
12
|
+
* Implements the digital-workers interface for humans operating within a company boundary.
|
|
13
|
+
*
|
|
14
|
+
* @packageDocumentation
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* import { Human, approve, ask, notify } from 'human-in-the-loop'
|
|
18
|
+
*
|
|
19
|
+
* // Create a Human-in-the-loop manager
|
|
20
|
+
* const human = Human({
|
|
21
|
+
* defaultTimeout: 3600000, // 1 hour
|
|
22
|
+
* autoEscalate: true,
|
|
23
|
+
* })
|
|
24
|
+
*
|
|
25
|
+
* // Request approval
|
|
26
|
+
* const result = await approve({
|
|
27
|
+
* title: 'Deploy to production',
|
|
28
|
+
* description: 'Approve deployment of v2.0.0',
|
|
29
|
+
* subject: 'Production Deployment',
|
|
30
|
+
* assignee: 'tech-lead@example.com',
|
|
31
|
+
* priority: 'high',
|
|
32
|
+
* })
|
|
33
|
+
*
|
|
34
|
+
* if (result.approved) {
|
|
35
|
+
* await deploy()
|
|
36
|
+
* await notify({
|
|
37
|
+
* type: 'success',
|
|
38
|
+
* title: 'Deployment complete',
|
|
39
|
+
* message: 'v2.0.0 deployed to production',
|
|
40
|
+
* recipient: 'team@example.com',
|
|
41
|
+
* })
|
|
42
|
+
* }
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
3
45
|
|
|
4
|
-
|
|
5
|
-
export
|
|
6
|
-
|
|
7
|
-
|
|
46
|
+
// Export main Human constructor and manager
|
|
47
|
+
export { Human, HumanManager } from './human.js'
|
|
48
|
+
|
|
49
|
+
// Export helper functions (convenience API)
|
|
50
|
+
export {
|
|
51
|
+
Role,
|
|
52
|
+
Team,
|
|
53
|
+
Goals,
|
|
54
|
+
approve,
|
|
55
|
+
ask,
|
|
56
|
+
do,
|
|
57
|
+
decide,
|
|
58
|
+
generate,
|
|
59
|
+
is,
|
|
60
|
+
notify,
|
|
61
|
+
kpis,
|
|
62
|
+
okrs,
|
|
63
|
+
registerHuman,
|
|
64
|
+
getDefaultHuman,
|
|
65
|
+
} from './helpers.js'
|
|
66
|
+
|
|
67
|
+
// Export store implementations
|
|
68
|
+
export { InMemoryHumanStore } from './store.js'
|
|
69
|
+
|
|
70
|
+
// Export all types
|
|
71
|
+
export type {
|
|
72
|
+
// Status and enums
|
|
73
|
+
HumanRequestStatus,
|
|
74
|
+
Priority,
|
|
75
|
+
|
|
76
|
+
// Core entities
|
|
77
|
+
Role as RoleType,
|
|
78
|
+
Team as TeamType,
|
|
79
|
+
Human as HumanType,
|
|
80
|
+
Goals as GoalsType,
|
|
81
|
+
KPIs,
|
|
82
|
+
OKRs,
|
|
83
|
+
|
|
84
|
+
// Request types
|
|
85
|
+
HumanRequest,
|
|
86
|
+
ApprovalRequest,
|
|
87
|
+
ApprovalResponse,
|
|
88
|
+
QuestionRequest,
|
|
89
|
+
TaskRequest,
|
|
90
|
+
DecisionRequest,
|
|
91
|
+
ReviewRequest,
|
|
92
|
+
ReviewResponse,
|
|
93
|
+
Notification,
|
|
94
|
+
|
|
95
|
+
// Management
|
|
96
|
+
ReviewQueue,
|
|
97
|
+
EscalationPolicy,
|
|
98
|
+
ApprovalWorkflow,
|
|
99
|
+
|
|
100
|
+
// Store interface
|
|
101
|
+
HumanStore,
|
|
102
|
+
HumanOptions,
|
|
103
|
+
} from './types.js'
|
package/src/store.ts
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory store implementation for human requests
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type {
|
|
6
|
+
HumanStore,
|
|
7
|
+
HumanRequest,
|
|
8
|
+
HumanRequestStatus,
|
|
9
|
+
ReviewQueue,
|
|
10
|
+
} from './types.js'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Simple in-memory implementation of HumanStore
|
|
14
|
+
*
|
|
15
|
+
* For production use, implement a persistent store using:
|
|
16
|
+
* - Database (PostgreSQL, MongoDB, etc.)
|
|
17
|
+
* - Key-value store (Redis)
|
|
18
|
+
* - Message queue (RabbitMQ, AWS SQS)
|
|
19
|
+
*/
|
|
20
|
+
export class InMemoryHumanStore implements HumanStore {
|
|
21
|
+
private requests = new Map<string, HumanRequest>()
|
|
22
|
+
private requestIdCounter = 0
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Generate a unique request ID
|
|
26
|
+
*/
|
|
27
|
+
private generateId(): string {
|
|
28
|
+
return `req_${Date.now()}_${++this.requestIdCounter}`
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Create a new request
|
|
33
|
+
*/
|
|
34
|
+
async create<T extends HumanRequest>(
|
|
35
|
+
request: Omit<T, 'id' | 'createdAt' | 'updatedAt'>
|
|
36
|
+
): Promise<T> {
|
|
37
|
+
const now = new Date()
|
|
38
|
+
const fullRequest = {
|
|
39
|
+
...request,
|
|
40
|
+
id: this.generateId(),
|
|
41
|
+
createdAt: now,
|
|
42
|
+
updatedAt: now,
|
|
43
|
+
} as T
|
|
44
|
+
|
|
45
|
+
this.requests.set(fullRequest.id, fullRequest)
|
|
46
|
+
return fullRequest
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Get a request by ID
|
|
51
|
+
*/
|
|
52
|
+
async get<T extends HumanRequest>(id: string): Promise<T | null> {
|
|
53
|
+
const request = this.requests.get(id)
|
|
54
|
+
return request ? (request as T) : null
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Update a request
|
|
59
|
+
*/
|
|
60
|
+
async update<T extends HumanRequest>(
|
|
61
|
+
id: string,
|
|
62
|
+
updates: Partial<T>
|
|
63
|
+
): Promise<T> {
|
|
64
|
+
const request = this.requests.get(id)
|
|
65
|
+
if (!request) {
|
|
66
|
+
throw new Error(`Request not found: ${id}`)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const updated = {
|
|
70
|
+
...request,
|
|
71
|
+
...updates,
|
|
72
|
+
updatedAt: new Date(),
|
|
73
|
+
} as T
|
|
74
|
+
|
|
75
|
+
this.requests.set(id, updated)
|
|
76
|
+
return updated
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* List requests with filters
|
|
81
|
+
*/
|
|
82
|
+
async list<T extends HumanRequest>(
|
|
83
|
+
filters?: ReviewQueue['filters'],
|
|
84
|
+
limit?: number
|
|
85
|
+
): Promise<T[]> {
|
|
86
|
+
let requests = Array.from(this.requests.values()) as T[]
|
|
87
|
+
|
|
88
|
+
// Apply filters
|
|
89
|
+
if (filters) {
|
|
90
|
+
if (filters.status) {
|
|
91
|
+
requests = requests.filter((r) => filters.status!.includes(r.status))
|
|
92
|
+
}
|
|
93
|
+
if (filters.priority) {
|
|
94
|
+
requests = requests.filter((r) => filters.priority!.includes(r.priority))
|
|
95
|
+
}
|
|
96
|
+
if (filters.assignee) {
|
|
97
|
+
requests = requests.filter((r) => {
|
|
98
|
+
if (!r.assignee) return false
|
|
99
|
+
const assignees = Array.isArray(r.assignee) ? r.assignee : [r.assignee]
|
|
100
|
+
return assignees.some((a) => filters.assignee!.includes(a))
|
|
101
|
+
})
|
|
102
|
+
}
|
|
103
|
+
if (filters.role) {
|
|
104
|
+
requests = requests.filter((r) => r.role && filters.role!.includes(r.role))
|
|
105
|
+
}
|
|
106
|
+
if (filters.team) {
|
|
107
|
+
requests = requests.filter((r) => r.team && filters.team!.includes(r.team))
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Sort by creation date (newest first)
|
|
112
|
+
requests.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime())
|
|
113
|
+
|
|
114
|
+
// Apply limit
|
|
115
|
+
if (limit && limit > 0) {
|
|
116
|
+
requests = requests.slice(0, limit)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return requests
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Complete a request
|
|
124
|
+
*/
|
|
125
|
+
async complete<T extends HumanRequest>(
|
|
126
|
+
id: string,
|
|
127
|
+
response: T['response']
|
|
128
|
+
): Promise<T> {
|
|
129
|
+
const request = await this.get<T>(id)
|
|
130
|
+
if (!request) {
|
|
131
|
+
throw new Error(`Request not found: ${id}`)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return this.update<T>(id, {
|
|
135
|
+
status: 'completed' as HumanRequestStatus,
|
|
136
|
+
response,
|
|
137
|
+
completedAt: new Date(),
|
|
138
|
+
} as Partial<T>)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Reject a request
|
|
143
|
+
*/
|
|
144
|
+
async reject(id: string, reason: string): Promise<HumanRequest> {
|
|
145
|
+
const request = await this.get(id)
|
|
146
|
+
if (!request) {
|
|
147
|
+
throw new Error(`Request not found: ${id}`)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return this.update(id, {
|
|
151
|
+
status: 'rejected' as HumanRequestStatus,
|
|
152
|
+
rejectionReason: reason,
|
|
153
|
+
completedAt: new Date(),
|
|
154
|
+
})
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Escalate a request
|
|
159
|
+
*/
|
|
160
|
+
async escalate(id: string, to: string): Promise<HumanRequest> {
|
|
161
|
+
const request = await this.get(id)
|
|
162
|
+
if (!request) {
|
|
163
|
+
throw new Error(`Request not found: ${id}`)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return this.update(id, {
|
|
167
|
+
status: 'escalated' as HumanRequestStatus,
|
|
168
|
+
assignee: to,
|
|
169
|
+
})
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Cancel a request
|
|
174
|
+
*/
|
|
175
|
+
async cancel(id: string): Promise<HumanRequest> {
|
|
176
|
+
const request = await this.get(id)
|
|
177
|
+
if (!request) {
|
|
178
|
+
throw new Error(`Request not found: ${id}`)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return this.update(id, {
|
|
182
|
+
status: 'cancelled' as HumanRequestStatus,
|
|
183
|
+
completedAt: new Date(),
|
|
184
|
+
})
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Clear all requests (for testing)
|
|
189
|
+
*/
|
|
190
|
+
clear(): void {
|
|
191
|
+
this.requests.clear()
|
|
192
|
+
this.requestIdCounter = 0
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Get total count of requests
|
|
197
|
+
*/
|
|
198
|
+
count(): number {
|
|
199
|
+
return this.requests.size
|
|
200
|
+
}
|
|
201
|
+
}
|