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/README.md
CHANGED
|
@@ -1,222 +1,656 @@
|
|
|
1
|
-
#
|
|
1
|
+
# human-in-the-loop
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Primitives for integrating human oversight and intervention in AI workflows. Implements the digital-workers interface for humans operating within a company boundary.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
This package provides a comprehensive toolkit for human-in-the-loop (HITL) workflows, enabling seamless integration of human judgment, approval, and intervention points in automated AI systems.
|
|
8
|
+
|
|
9
|
+
**Key Features:**
|
|
10
|
+
|
|
11
|
+
- 🎯 **Approval Workflows** - Multi-step approval gates with escalation
|
|
12
|
+
- ❓ **Question & Answer** - Ask humans for information or guidance
|
|
13
|
+
- 📋 **Task Assignment** - Delegate tasks that require human judgment
|
|
14
|
+
- 🔀 **Decision Points** - Request human decisions between options
|
|
15
|
+
- 👀 **Review Processes** - Code, content, design, and data reviews
|
|
16
|
+
- 📬 **Notifications** - Multi-channel notifications (Slack, email, SMS, web)
|
|
17
|
+
- 👥 **Role & Team Management** - Define roles, teams, and capabilities
|
|
18
|
+
- 📊 **Goals & OKRs** - Track objectives, KPIs, and key results
|
|
19
|
+
- ⏰ **Timeouts & Escalation** - Automatic escalation on timeout
|
|
20
|
+
- 🔄 **Review Queues** - Organize and prioritize pending requests
|
|
4
21
|
|
|
5
22
|
## Installation
|
|
6
23
|
|
|
7
24
|
```bash
|
|
8
|
-
npm install human-in-the-loop
|
|
9
|
-
# or
|
|
10
|
-
yarn add human-in-the-loop
|
|
11
|
-
# or
|
|
12
25
|
pnpm add human-in-the-loop
|
|
13
26
|
```
|
|
14
27
|
|
|
15
|
-
##
|
|
28
|
+
## Quick Start
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
import { Human, approve, ask, notify } from 'human-in-the-loop'
|
|
32
|
+
|
|
33
|
+
// Create a Human-in-the-loop manager
|
|
34
|
+
const human = Human({
|
|
35
|
+
defaultTimeout: 3600000, // 1 hour
|
|
36
|
+
autoEscalate: true,
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
// Request approval
|
|
40
|
+
const result = await approve({
|
|
41
|
+
title: 'Deploy to production',
|
|
42
|
+
description: 'Approve deployment of v2.0.0',
|
|
43
|
+
subject: 'Production Deployment',
|
|
44
|
+
input: { version: '2.0.0' },
|
|
45
|
+
assignee: 'tech-lead@example.com',
|
|
46
|
+
priority: 'high',
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
if (result.approved) {
|
|
50
|
+
await deploy()
|
|
51
|
+
await notify({
|
|
52
|
+
type: 'success',
|
|
53
|
+
title: 'Deployment complete',
|
|
54
|
+
message: 'v2.0.0 deployed to production',
|
|
55
|
+
recipient: 'team@example.com',
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## API Reference
|
|
16
61
|
|
|
17
|
-
|
|
18
|
-
- Support for multiple platforms:
|
|
19
|
-
- Slack (using Block Kit)
|
|
20
|
-
- Microsoft Teams
|
|
21
|
-
- React/ShadCN UI components
|
|
22
|
-
- Email (via React Email)
|
|
23
|
-
- Simple, consistent API across all platforms
|
|
24
|
-
- Type-safe inputs and outputs
|
|
62
|
+
### Core Functions
|
|
25
63
|
|
|
26
|
-
|
|
64
|
+
#### `Human(options?)`
|
|
27
65
|
|
|
28
|
-
|
|
66
|
+
Create a Human-in-the-loop manager instance.
|
|
29
67
|
|
|
30
68
|
```typescript
|
|
31
|
-
|
|
69
|
+
const human = Human({
|
|
70
|
+
defaultTimeout: 3600000, // Default timeout in ms
|
|
71
|
+
defaultPriority: 'normal', // Default priority level
|
|
72
|
+
autoEscalate: true, // Auto-escalate on timeout
|
|
73
|
+
escalationPolicies: [...], // Escalation policies
|
|
74
|
+
store: customStore, // Custom storage backend
|
|
75
|
+
})
|
|
76
|
+
```
|
|
32
77
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
title: '
|
|
40
|
-
description: '
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
channel: 'approvals'
|
|
78
|
+
#### `approve(params)`
|
|
79
|
+
|
|
80
|
+
Request approval from a human.
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
const result = await approve({
|
|
84
|
+
title: 'Expense Approval',
|
|
85
|
+
description: 'Approve employee expense claim',
|
|
86
|
+
subject: 'Expense Claim #1234',
|
|
87
|
+
input: { amount: 150, category: 'Travel' },
|
|
88
|
+
assignee: 'manager@example.com',
|
|
89
|
+
priority: 'normal',
|
|
90
|
+
timeout: 86400000, // 24 hours
|
|
91
|
+
escalatesTo: 'director@example.com',
|
|
48
92
|
})
|
|
49
93
|
|
|
50
|
-
//
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
94
|
+
// result: { approved: boolean, comments?: string, conditions?: string[] }
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
#### `ask(params)`
|
|
98
|
+
|
|
99
|
+
Ask a question to a human.
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
const answer = await ask({
|
|
103
|
+
title: 'Product naming',
|
|
104
|
+
question: 'What should we name the new feature?',
|
|
105
|
+
context: { feature: 'AI Assistant' },
|
|
106
|
+
assignee: 'product-manager@example.com',
|
|
107
|
+
suggestions: ['CodeMate', 'DevAssist', 'AIHelper'],
|
|
54
108
|
})
|
|
55
109
|
|
|
56
|
-
|
|
110
|
+
// answer: string
|
|
111
|
+
```
|
|
57
112
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
113
|
+
#### `do(params)`
|
|
114
|
+
|
|
115
|
+
Request a human to perform a task (implements digital-workers interface).
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
const result = await do({
|
|
119
|
+
title: 'Review code',
|
|
120
|
+
instructions: 'Review the PR and provide feedback',
|
|
121
|
+
input: { prUrl: 'https://github.com/...' },
|
|
122
|
+
assignee: 'senior-dev@example.com',
|
|
123
|
+
estimatedEffort: '30 minutes',
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
// result: TOutput (task-specific)
|
|
68
127
|
```
|
|
69
128
|
|
|
70
|
-
|
|
129
|
+
#### `decide(params)`
|
|
71
130
|
|
|
72
|
-
|
|
131
|
+
Request a human to make a decision.
|
|
73
132
|
|
|
74
133
|
```typescript
|
|
75
|
-
|
|
134
|
+
const strategy = await decide({
|
|
135
|
+
title: 'Deployment strategy',
|
|
136
|
+
options: ['blue-green', 'canary', 'rolling'],
|
|
137
|
+
context: { risk: 'high', users: 100000 },
|
|
138
|
+
criteria: ['Minimize risk', 'Fast rollback'],
|
|
139
|
+
assignee: 'devops-lead@example.com',
|
|
140
|
+
})
|
|
76
141
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
142
|
+
// strategy: 'blue-green' | 'canary' | 'rolling'
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
#### `generate(params)`
|
|
146
|
+
|
|
147
|
+
Request content generation from a human (specialized form of `do()`).
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
const content = await generate({
|
|
151
|
+
title: 'Write blog post',
|
|
152
|
+
instructions: 'Write about our new AI features',
|
|
153
|
+
input: { topic: 'AI Assistant', targetAudience: 'developers' },
|
|
154
|
+
assignee: 'content-writer@example.com',
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
// content: string
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
#### `is(params)`
|
|
161
|
+
|
|
162
|
+
Request validation/type checking from a human.
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
const valid = await is({
|
|
166
|
+
title: 'Validate data',
|
|
167
|
+
question: 'Is this user data valid and complete?',
|
|
168
|
+
input: userData,
|
|
169
|
+
assignee: 'data-specialist@example.com',
|
|
93
170
|
})
|
|
171
|
+
|
|
172
|
+
// valid: boolean
|
|
94
173
|
```
|
|
95
174
|
|
|
96
|
-
####
|
|
175
|
+
#### `notify(params)`
|
|
176
|
+
|
|
177
|
+
Send a notification to a human.
|
|
97
178
|
|
|
98
179
|
```typescript
|
|
99
|
-
|
|
180
|
+
await notify({
|
|
181
|
+
type: 'info', // 'info' | 'warning' | 'error' | 'success'
|
|
182
|
+
title: 'Deployment complete',
|
|
183
|
+
message: 'Version 2.0.0 deployed successfully',
|
|
184
|
+
recipient: 'team@example.com',
|
|
185
|
+
channels: ['slack', 'email'], // Optional
|
|
186
|
+
priority: 'normal',
|
|
187
|
+
})
|
|
188
|
+
```
|
|
100
189
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
})
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
####
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
190
|
+
### Role & Team Management
|
|
191
|
+
|
|
192
|
+
#### `Role(definition)`
|
|
193
|
+
|
|
194
|
+
Define a human role.
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
const techLead = Role({
|
|
198
|
+
id: 'tech-lead',
|
|
199
|
+
name: 'Tech Lead',
|
|
200
|
+
description: 'Technical leadership',
|
|
201
|
+
capabilities: ['approve-prs', 'deploy-prod'],
|
|
202
|
+
escalatesTo: 'engineering-manager',
|
|
203
|
+
})
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
#### `Team(definition)`
|
|
207
|
+
|
|
208
|
+
Define a team.
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
const engineering = Team({
|
|
212
|
+
id: 'engineering',
|
|
213
|
+
name: 'Engineering Team',
|
|
214
|
+
members: ['alice', 'bob', 'charlie'],
|
|
215
|
+
lead: 'alice',
|
|
216
|
+
})
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
#### `registerHuman(human)`
|
|
220
|
+
|
|
221
|
+
Register a human worker.
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
const alice = registerHuman({
|
|
225
|
+
id: 'alice',
|
|
226
|
+
name: 'Alice Smith',
|
|
227
|
+
email: 'alice@example.com',
|
|
228
|
+
roles: ['tech-lead'],
|
|
229
|
+
teams: ['engineering'],
|
|
230
|
+
channels: {
|
|
231
|
+
slack: '@alice',
|
|
232
|
+
email: 'alice@example.com',
|
|
233
|
+
},
|
|
234
|
+
})
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### Goals & Performance Tracking
|
|
238
|
+
|
|
239
|
+
#### `Goals(definition)`
|
|
240
|
+
|
|
241
|
+
Define goals for a team or individual.
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
const q1Goals = Goals({
|
|
245
|
+
id: 'q1-2024',
|
|
246
|
+
objectives: ['Launch v2.0', 'Improve performance by 50%'],
|
|
247
|
+
successCriteria: ['Release by March 31', 'Pass benchmarks'],
|
|
248
|
+
targetDate: new Date('2024-03-31'),
|
|
249
|
+
})
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
#### `kpis(kpi)`
|
|
253
|
+
|
|
254
|
+
Track Key Performance Indicators.
|
|
255
|
+
|
|
256
|
+
```typescript
|
|
257
|
+
kpis({
|
|
258
|
+
id: 'response-time',
|
|
259
|
+
name: 'API Response Time',
|
|
260
|
+
value: 120,
|
|
261
|
+
target: 100,
|
|
262
|
+
unit: 'ms',
|
|
263
|
+
trend: 'down',
|
|
264
|
+
})
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
#### `okrs(okr)`
|
|
268
|
+
|
|
269
|
+
Define Objectives and Key Results.
|
|
270
|
+
|
|
271
|
+
```typescript
|
|
272
|
+
okrs({
|
|
273
|
+
id: 'q1-2024-growth',
|
|
274
|
+
objective: 'Accelerate user growth',
|
|
275
|
+
keyResults: [
|
|
276
|
+
{
|
|
277
|
+
description: 'Increase active users by 50%',
|
|
278
|
+
progress: 0.3,
|
|
279
|
+
current: 13000,
|
|
280
|
+
target: 15000,
|
|
281
|
+
},
|
|
137
282
|
],
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
})
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
283
|
+
period: 'Q1 2024',
|
|
284
|
+
owner: 'ceo@example.com',
|
|
285
|
+
})
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
## Advanced Usage
|
|
289
|
+
|
|
290
|
+
### Custom Store Implementation
|
|
291
|
+
|
|
292
|
+
Implement a custom storage backend:
|
|
293
|
+
|
|
294
|
+
```typescript
|
|
295
|
+
import { HumanStore } from 'human-in-the-loop'
|
|
296
|
+
|
|
297
|
+
class DatabaseHumanStore implements HumanStore {
|
|
298
|
+
async create(request) {
|
|
299
|
+
// Save to database
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
async get(id) {
|
|
303
|
+
// Fetch from database
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
async update(id, updates) {
|
|
307
|
+
// Update in database
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
async complete(id, response) {
|
|
311
|
+
// Complete request
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
async reject(id, reason) {
|
|
315
|
+
// Reject request
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
async escalate(id, to) {
|
|
319
|
+
// Escalate request
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
async cancel(id) {
|
|
323
|
+
// Cancel request
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
async list(filters, limit) {
|
|
327
|
+
// List requests with filters
|
|
149
328
|
}
|
|
150
|
-
|
|
151
|
-
return (
|
|
152
|
-
<div>
|
|
153
|
-
<button onClick={handleRequestFeedback}>Request Feedback</button>
|
|
154
|
-
|
|
155
|
-
{taskId && reactFeedback.getComponent(taskId, { productName: 'Super Product' })}
|
|
156
|
-
</div>
|
|
157
|
-
)
|
|
158
329
|
}
|
|
330
|
+
|
|
331
|
+
const human = Human({
|
|
332
|
+
store: new DatabaseHumanStore(),
|
|
333
|
+
})
|
|
159
334
|
```
|
|
160
335
|
|
|
161
|
-
|
|
336
|
+
### Escalation Policies
|
|
337
|
+
|
|
338
|
+
Define automatic escalation policies:
|
|
162
339
|
|
|
163
340
|
```typescript
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
341
|
+
const human = Human({
|
|
342
|
+
autoEscalate: true,
|
|
343
|
+
escalationPolicies: [
|
|
344
|
+
{
|
|
345
|
+
id: 'critical-approval',
|
|
346
|
+
name: 'Critical Approval Policy',
|
|
347
|
+
conditions: {
|
|
348
|
+
timeout: 1800000, // 30 minutes
|
|
349
|
+
minPriority: 'critical',
|
|
350
|
+
},
|
|
351
|
+
escalationPath: [
|
|
352
|
+
{
|
|
353
|
+
assignee: 'tech-lead',
|
|
354
|
+
afterMs: 1800000, // 30 minutes
|
|
355
|
+
notifyVia: ['slack', 'sms'],
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
assignee: 'engineering-manager',
|
|
359
|
+
afterMs: 3600000, // 1 hour
|
|
360
|
+
notifyVia: ['slack', 'email', 'sms'],
|
|
361
|
+
},
|
|
362
|
+
],
|
|
363
|
+
},
|
|
364
|
+
],
|
|
365
|
+
})
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
### Approval Workflows
|
|
167
369
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
{
|
|
370
|
+
Create multi-step approval workflows:
|
|
371
|
+
|
|
372
|
+
```typescript
|
|
373
|
+
const workflow = human.createWorkflow({
|
|
374
|
+
id: 'prod-deployment-workflow',
|
|
375
|
+
name: 'Production Deployment Workflow',
|
|
376
|
+
steps: [
|
|
377
|
+
{
|
|
378
|
+
name: 'Code Review',
|
|
379
|
+
role: 'engineer',
|
|
380
|
+
approvers: ['bob'],
|
|
381
|
+
requireAll: true,
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
name: 'Security Review',
|
|
385
|
+
role: 'security-engineer',
|
|
386
|
+
approvers: ['security-team'],
|
|
387
|
+
requireAll: false, // Any one approver
|
|
388
|
+
},
|
|
389
|
+
{
|
|
390
|
+
name: 'Tech Lead Approval',
|
|
391
|
+
role: 'tech-lead',
|
|
392
|
+
approvers: ['alice'],
|
|
393
|
+
requireAll: true,
|
|
394
|
+
},
|
|
182
395
|
],
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
396
|
+
})
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
### Review Queues
|
|
400
|
+
|
|
401
|
+
Organize and prioritize pending requests:
|
|
402
|
+
|
|
403
|
+
```typescript
|
|
404
|
+
const queue = await human.getQueue({
|
|
405
|
+
name: 'High Priority Queue',
|
|
406
|
+
description: 'All high priority pending requests',
|
|
407
|
+
filters: {
|
|
408
|
+
status: ['pending', 'in_progress'],
|
|
409
|
+
priority: ['high', 'critical'],
|
|
410
|
+
},
|
|
411
|
+
sortBy: 'priority',
|
|
412
|
+
sortDirection: 'desc',
|
|
413
|
+
limit: 10,
|
|
414
|
+
})
|
|
415
|
+
|
|
416
|
+
console.log(`Queue: ${queue.name}`)
|
|
417
|
+
console.log(`Items: ${queue.items.length}`)
|
|
187
418
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
const emailHtml = render(emailComponent)
|
|
419
|
+
for (const item of queue.items) {
|
|
420
|
+
console.log(`- ${item.title} (${item.priority})`)
|
|
421
|
+
}
|
|
192
422
|
```
|
|
193
423
|
|
|
194
|
-
|
|
424
|
+
### Request Management
|
|
195
425
|
|
|
196
|
-
|
|
426
|
+
Manually manage requests:
|
|
197
427
|
|
|
198
|
-
|
|
428
|
+
```typescript
|
|
429
|
+
// Get a request
|
|
430
|
+
const request = await human.getRequest('req_123')
|
|
431
|
+
|
|
432
|
+
// Complete with response
|
|
433
|
+
await human.completeRequest('req_123', {
|
|
434
|
+
approved: true,
|
|
435
|
+
comments: 'Looks good!',
|
|
436
|
+
})
|
|
437
|
+
|
|
438
|
+
// Reject
|
|
439
|
+
await human.rejectRequest('req_123', 'Not ready yet')
|
|
440
|
+
|
|
441
|
+
// Escalate
|
|
442
|
+
await human.escalateRequest('req_123', 'manager@example.com')
|
|
443
|
+
|
|
444
|
+
// Cancel
|
|
445
|
+
await human.cancelRequest('req_123')
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
## Integration with digital-workers
|
|
449
|
+
|
|
450
|
+
Human-in-the-loop implements the `Worker` interface from `digital-workers`, enabling humans to:
|
|
451
|
+
- Receive notifications, questions, and approval requests via Worker Actions
|
|
452
|
+
- Be targeted by workflow actions
|
|
453
|
+
- Communicate through configured contact channels
|
|
454
|
+
|
|
455
|
+
### Using Worker Actions in Workflows
|
|
456
|
+
|
|
457
|
+
```typescript
|
|
458
|
+
import { Workflow } from 'ai-workflows'
|
|
459
|
+
import { registerWorkerActions, withWorkers } from 'digital-workers'
|
|
460
|
+
import type { Worker } from 'digital-workers'
|
|
461
|
+
|
|
462
|
+
// Define a human worker with contacts
|
|
463
|
+
const alice: Worker = {
|
|
464
|
+
id: 'alice',
|
|
465
|
+
name: 'Alice',
|
|
466
|
+
type: 'human',
|
|
467
|
+
status: 'available',
|
|
468
|
+
contacts: {
|
|
469
|
+
email: 'alice@company.com',
|
|
470
|
+
slack: { workspace: 'company', user: 'U123' },
|
|
471
|
+
phone: '+1-555-1234',
|
|
472
|
+
},
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
const workflow = Workflow($ => {
|
|
476
|
+
registerWorkerActions($)
|
|
477
|
+
const worker$ = withWorkers($)
|
|
478
|
+
|
|
479
|
+
$.on.Expense.submitted(async (expense) => {
|
|
480
|
+
// Request approval from manager using Worker Actions
|
|
481
|
+
const approval = await worker$.approve(
|
|
482
|
+
`Expense: $${expense.amount} for ${expense.description}`,
|
|
483
|
+
alice,
|
|
484
|
+
{ via: 'slack', timeout: 86400000 }
|
|
485
|
+
)
|
|
486
|
+
|
|
487
|
+
if (approval.approved) {
|
|
488
|
+
await worker$.notify(
|
|
489
|
+
expense.submitter,
|
|
490
|
+
'Your expense has been approved!',
|
|
491
|
+
{ via: 'email' }
|
|
492
|
+
)
|
|
493
|
+
}
|
|
494
|
+
})
|
|
495
|
+
|
|
496
|
+
$.on.Deployment.requested(async (deploy) => {
|
|
497
|
+
// Ask human for confirmation
|
|
498
|
+
const confirmed = await worker$.ask(
|
|
499
|
+
alice,
|
|
500
|
+
`Proceed with deployment of ${deploy.version} to ${deploy.env}?`,
|
|
501
|
+
{ schema: { proceed: 'boolean', notes: 'string' } }
|
|
502
|
+
)
|
|
503
|
+
|
|
504
|
+
if (confirmed.answer.proceed) {
|
|
505
|
+
$.send('Deployment.approved', deploy)
|
|
506
|
+
}
|
|
507
|
+
})
|
|
508
|
+
})
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
### Human as Worker Target
|
|
512
|
+
|
|
513
|
+
```typescript
|
|
514
|
+
import { registerHuman } from 'human-in-the-loop'
|
|
515
|
+
import { notify, ask, approve } from 'digital-workers'
|
|
516
|
+
|
|
517
|
+
// Register a human worker
|
|
518
|
+
const manager = registerHuman({
|
|
519
|
+
id: 'manager',
|
|
520
|
+
name: 'Manager',
|
|
521
|
+
email: 'manager@company.com',
|
|
522
|
+
roles: ['approver'],
|
|
523
|
+
teams: ['finance'],
|
|
524
|
+
channels: {
|
|
525
|
+
slack: '@manager',
|
|
526
|
+
email: 'manager@company.com',
|
|
527
|
+
},
|
|
528
|
+
})
|
|
529
|
+
|
|
530
|
+
// Use digital-workers actions directly
|
|
531
|
+
await notify(manager, 'Weekly report ready', { via: 'email' })
|
|
199
532
|
|
|
200
|
-
|
|
533
|
+
const answer = await ask(manager, 'What is the Q2 budget?', {
|
|
534
|
+
via: 'slack',
|
|
535
|
+
schema: { amount: 'number', notes: 'string' },
|
|
536
|
+
})
|
|
201
537
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
- `timeout`: Timeout in milliseconds after which the task is marked as timed out
|
|
538
|
+
const approval = await approve('Hire contractor', manager, {
|
|
539
|
+
via: 'slack',
|
|
540
|
+
context: { role: 'Senior Developer', rate: '$150/hr' },
|
|
541
|
+
})
|
|
542
|
+
```
|
|
208
543
|
|
|
209
|
-
|
|
544
|
+
## Integration Examples
|
|
210
545
|
|
|
211
|
-
###
|
|
546
|
+
### With AI Workflows
|
|
212
547
|
|
|
213
|
-
|
|
548
|
+
```typescript
|
|
549
|
+
import { Human } from 'human-in-the-loop'
|
|
550
|
+
import { AI } from 'ai-functions'
|
|
551
|
+
|
|
552
|
+
const human = Human()
|
|
553
|
+
const ai = AI()
|
|
554
|
+
|
|
555
|
+
async function processWithOversight(data: unknown) {
|
|
556
|
+
// AI generates initial result
|
|
557
|
+
const result = await ai.analyze(data)
|
|
558
|
+
|
|
559
|
+
// Human reviews before proceeding
|
|
560
|
+
const review = await human.review({
|
|
561
|
+
title: 'Review AI analysis',
|
|
562
|
+
content: result,
|
|
563
|
+
reviewType: 'data',
|
|
564
|
+
criteria: ['Accuracy', 'Completeness', 'Bias'],
|
|
565
|
+
assignee: 'data-analyst@example.com',
|
|
566
|
+
})
|
|
567
|
+
|
|
568
|
+
if (review.approved) {
|
|
569
|
+
return result
|
|
570
|
+
} else {
|
|
571
|
+
// Request human to fix
|
|
572
|
+
return await human.do({
|
|
573
|
+
title: 'Fix analysis',
|
|
574
|
+
instructions: review.comments,
|
|
575
|
+
input: { original: result, feedback: review.changes },
|
|
576
|
+
assignee: 'data-analyst@example.com',
|
|
577
|
+
})
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
### With MCP Tools
|
|
214
583
|
|
|
215
|
-
|
|
584
|
+
```typescript
|
|
585
|
+
import { createMCPServer } from '@mdxe/mcp'
|
|
586
|
+
import { Human } from 'human-in-the-loop'
|
|
587
|
+
|
|
588
|
+
const human = Human()
|
|
589
|
+
|
|
590
|
+
const mcpServer = createMCPServer({
|
|
591
|
+
tools: {
|
|
592
|
+
request_approval: {
|
|
593
|
+
description: 'Request human approval',
|
|
594
|
+
parameters: {
|
|
595
|
+
type: 'object',
|
|
596
|
+
properties: {
|
|
597
|
+
title: { type: 'string' },
|
|
598
|
+
description: { type: 'string' },
|
|
599
|
+
assignee: { type: 'string' },
|
|
600
|
+
},
|
|
601
|
+
required: ['title', 'description'],
|
|
602
|
+
},
|
|
603
|
+
handler: async (params) => {
|
|
604
|
+
const result = await human.approve({
|
|
605
|
+
title: params.title,
|
|
606
|
+
description: params.description,
|
|
607
|
+
subject: params.title,
|
|
608
|
+
input: params,
|
|
609
|
+
assignee: params.assignee,
|
|
610
|
+
})
|
|
611
|
+
return result
|
|
612
|
+
},
|
|
613
|
+
},
|
|
614
|
+
},
|
|
615
|
+
})
|
|
616
|
+
```
|
|
216
617
|
|
|
217
|
-
|
|
218
|
-
|
|
618
|
+
## Architecture
|
|
619
|
+
|
|
620
|
+
The package follows a clean architecture with clear separation of concerns:
|
|
621
|
+
|
|
622
|
+
```
|
|
623
|
+
human-in-the-loop/
|
|
624
|
+
├── src/
|
|
625
|
+
│ ├── types.ts # Type definitions
|
|
626
|
+
│ ├── store.ts # Storage implementation
|
|
627
|
+
│ ├── human.ts # Core HumanManager class
|
|
628
|
+
│ ├── helpers.ts # Convenience functions
|
|
629
|
+
│ └── index.ts # Public exports
|
|
630
|
+
├── examples/
|
|
631
|
+
│ └── basic-usage.ts # Usage examples
|
|
632
|
+
└── README.md
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
**Design Principles:**
|
|
636
|
+
|
|
637
|
+
- **Store-agnostic**: Works with any storage backend (in-memory, database, queue)
|
|
638
|
+
- **Channel-flexible**: Supports multiple notification channels
|
|
639
|
+
- **Type-safe**: Full TypeScript support with strict types
|
|
640
|
+
- **Extensible**: Easy to add custom workflows and policies
|
|
641
|
+
- **Digital Workers Interface**: Compatible with the digital-workers abstraction
|
|
642
|
+
|
|
643
|
+
## Related Packages
|
|
644
|
+
|
|
645
|
+
- **ai-functions**: Core AI primitives
|
|
646
|
+
- **digital-workers**: Abstract interface over AI agents and humans
|
|
647
|
+
- **ai-workflows**: Event-driven AI workflows
|
|
648
|
+
- **@mdxe/mcp**: Model Context Protocol integration
|
|
219
649
|
|
|
220
650
|
## License
|
|
221
651
|
|
|
222
652
|
MIT
|
|
653
|
+
|
|
654
|
+
## Contributing
|
|
655
|
+
|
|
656
|
+
Contributions welcome! Please see the main repository for guidelines.
|