digital-workers 2.0.1 → 2.1.1
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/CHANGELOG.md +25 -0
- package/package.json +3 -4
- package/src/actions.js +436 -0
- package/src/approve.js +234 -0
- package/src/ask.js +226 -0
- package/src/decide.js +244 -0
- package/src/do.js +227 -0
- package/src/generate.js +298 -0
- package/src/goals.js +205 -0
- package/src/index.js +68 -0
- package/src/is.js +317 -0
- package/src/kpis.js +270 -0
- package/src/notify.js +219 -0
- package/src/role.js +110 -0
- package/src/team.js +130 -0
- package/src/transports.js +357 -0
- package/src/types.js +71 -0
- package/test/actions.test.js +401 -0
- package/test/standalone.test.js +250 -0
- package/test/types.test.js +371 -0
package/src/team.js
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Team definition for digital workers
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Define a team of workers
|
|
6
|
+
*
|
|
7
|
+
* Teams organize workers (AI agents and humans) into cohesive units
|
|
8
|
+
* with shared goals and responsibilities.
|
|
9
|
+
*
|
|
10
|
+
* @param definition - Team definition
|
|
11
|
+
* @returns The defined team
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* const engineeringTeam = Team({
|
|
16
|
+
* name: 'Engineering',
|
|
17
|
+
* description: 'Product engineering team',
|
|
18
|
+
* members: [
|
|
19
|
+
* { id: 'alice', name: 'Alice', role: 'Senior Engineer', type: 'human' },
|
|
20
|
+
* { id: 'bob', name: 'Bob', role: 'Engineer', type: 'human' },
|
|
21
|
+
* { id: 'ai-reviewer', name: 'Code Reviewer', role: 'Code Reviewer', type: 'ai' },
|
|
22
|
+
* { id: 'ai-tester', name: 'Test Generator', role: 'Test Engineer', type: 'ai' },
|
|
23
|
+
* ],
|
|
24
|
+
* goals: [
|
|
25
|
+
* 'Ship features on schedule',
|
|
26
|
+
* 'Maintain code quality',
|
|
27
|
+
* 'Reduce technical debt',
|
|
28
|
+
* ],
|
|
29
|
+
* lead: 'alice',
|
|
30
|
+
* })
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```ts
|
|
35
|
+
* const supportTeam = Team({
|
|
36
|
+
* name: 'Customer Support',
|
|
37
|
+
* description: '24/7 customer support team',
|
|
38
|
+
* members: [
|
|
39
|
+
* { id: 'support-ai-1', name: 'Support Bot Alpha', role: 'Support Agent', type: 'ai' },
|
|
40
|
+
* { id: 'support-ai-2', name: 'Support Bot Beta', role: 'Support Agent', type: 'ai' },
|
|
41
|
+
* { id: 'escalation-human', name: 'Jane', role: 'Senior Support', type: 'human' },
|
|
42
|
+
* ],
|
|
43
|
+
* goals: [
|
|
44
|
+
* 'Maintain 95% satisfaction rate',
|
|
45
|
+
* 'Response time under 5 minutes',
|
|
46
|
+
* 'First contact resolution > 80%',
|
|
47
|
+
* ],
|
|
48
|
+
* lead: 'escalation-human',
|
|
49
|
+
* })
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export function Team(definition) {
|
|
53
|
+
return definition;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Add a member to a team
|
|
57
|
+
*
|
|
58
|
+
* @param team - The team to add to
|
|
59
|
+
* @param member - The member to add
|
|
60
|
+
* @returns Updated team
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```ts
|
|
64
|
+
* const updatedTeam = Team.addMember(engineeringTeam, {
|
|
65
|
+
* id: 'charlie',
|
|
66
|
+
* name: 'Charlie',
|
|
67
|
+
* role: 'Junior Engineer',
|
|
68
|
+
* type: 'human',
|
|
69
|
+
* })
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
Team.addMember = (team, member) => ({
|
|
73
|
+
...team,
|
|
74
|
+
members: [...team.members, member],
|
|
75
|
+
});
|
|
76
|
+
/**
|
|
77
|
+
* Remove a member from a team
|
|
78
|
+
*
|
|
79
|
+
* @param team - The team to remove from
|
|
80
|
+
* @param memberId - ID of the member to remove
|
|
81
|
+
* @returns Updated team
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```ts
|
|
85
|
+
* const updatedTeam = Team.removeMember(engineeringTeam, 'bob')
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
Team.removeMember = (team, memberId) => ({
|
|
89
|
+
...team,
|
|
90
|
+
members: team.members.filter((m) => m.id !== memberId),
|
|
91
|
+
});
|
|
92
|
+
/**
|
|
93
|
+
* Get all AI members of a team
|
|
94
|
+
*
|
|
95
|
+
* @param team - The team
|
|
96
|
+
* @returns Array of AI members
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* ```ts
|
|
100
|
+
* const aiMembers = Team.aiMembers(supportTeam)
|
|
101
|
+
* console.log(aiMembers) // [Support Bot Alpha, Support Bot Beta]
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
Team.aiMembers = (team) => team.members.filter((m) => m.type === 'agent');
|
|
105
|
+
/**
|
|
106
|
+
* Get all human members of a team
|
|
107
|
+
*
|
|
108
|
+
* @param team - The team
|
|
109
|
+
* @returns Array of human members
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* ```ts
|
|
113
|
+
* const humans = Team.humanMembers(engineeringTeam)
|
|
114
|
+
* console.log(humans) // [Alice, Bob]
|
|
115
|
+
* ```
|
|
116
|
+
*/
|
|
117
|
+
Team.humanMembers = (team) => team.members.filter((m) => m.type === 'human');
|
|
118
|
+
/**
|
|
119
|
+
* Get members by role
|
|
120
|
+
*
|
|
121
|
+
* @param team - The team
|
|
122
|
+
* @param role - Role to filter by
|
|
123
|
+
* @returns Array of members with that role
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* ```ts
|
|
127
|
+
* const engineers = Team.byRole(engineeringTeam, 'Engineer')
|
|
128
|
+
* ```
|
|
129
|
+
*/
|
|
130
|
+
Team.byRole = (team, role) => team.members.filter((m) => m.role === role);
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Communication Transports Bridge
|
|
3
|
+
*
|
|
4
|
+
* Connects digital-workers contact channels to digital-tools
|
|
5
|
+
* communication providers (Message, Call).
|
|
6
|
+
*
|
|
7
|
+
* @packageDocumentation
|
|
8
|
+
*/
|
|
9
|
+
// =============================================================================
|
|
10
|
+
// Channel to Transport Mapping
|
|
11
|
+
// =============================================================================
|
|
12
|
+
/**
|
|
13
|
+
* Map contact channel to transport
|
|
14
|
+
*/
|
|
15
|
+
export function channelToTransport(channel) {
|
|
16
|
+
const mapping = {
|
|
17
|
+
email: 'email',
|
|
18
|
+
slack: 'slack',
|
|
19
|
+
teams: 'teams',
|
|
20
|
+
discord: 'discord',
|
|
21
|
+
phone: 'voice',
|
|
22
|
+
sms: 'sms',
|
|
23
|
+
whatsapp: 'whatsapp',
|
|
24
|
+
telegram: 'telegram',
|
|
25
|
+
web: 'web',
|
|
26
|
+
api: 'webhook',
|
|
27
|
+
webhook: 'webhook',
|
|
28
|
+
};
|
|
29
|
+
return mapping[channel] || 'webhook';
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Get available transports for a worker
|
|
33
|
+
*/
|
|
34
|
+
export function getWorkerTransports(worker) {
|
|
35
|
+
const transports = [];
|
|
36
|
+
const contacts = worker.contacts;
|
|
37
|
+
if (contacts.email)
|
|
38
|
+
transports.push('email');
|
|
39
|
+
if (contacts.slack)
|
|
40
|
+
transports.push('slack');
|
|
41
|
+
if (contacts.teams)
|
|
42
|
+
transports.push('teams');
|
|
43
|
+
if (contacts.discord)
|
|
44
|
+
transports.push('discord');
|
|
45
|
+
if (contacts.phone)
|
|
46
|
+
transports.push('voice');
|
|
47
|
+
if (contacts.sms)
|
|
48
|
+
transports.push('sms');
|
|
49
|
+
if (contacts.whatsapp)
|
|
50
|
+
transports.push('whatsapp');
|
|
51
|
+
if (contacts.telegram)
|
|
52
|
+
transports.push('telegram');
|
|
53
|
+
if (contacts.web)
|
|
54
|
+
transports.push('web');
|
|
55
|
+
if (contacts.webhook)
|
|
56
|
+
transports.push('webhook');
|
|
57
|
+
return transports;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Get team transports (union of all member transports + team contacts)
|
|
61
|
+
*/
|
|
62
|
+
export function getTeamTransports(team) {
|
|
63
|
+
const transports = new Set();
|
|
64
|
+
// Add team-level contacts
|
|
65
|
+
const contacts = team.contacts;
|
|
66
|
+
if (contacts.email)
|
|
67
|
+
transports.add('email');
|
|
68
|
+
if (contacts.slack)
|
|
69
|
+
transports.add('slack');
|
|
70
|
+
if (contacts.teams)
|
|
71
|
+
transports.add('teams');
|
|
72
|
+
if (contacts.discord)
|
|
73
|
+
transports.add('discord');
|
|
74
|
+
if (contacts.phone)
|
|
75
|
+
transports.add('voice');
|
|
76
|
+
if (contacts.sms)
|
|
77
|
+
transports.add('sms');
|
|
78
|
+
if (contacts.whatsapp)
|
|
79
|
+
transports.add('whatsapp');
|
|
80
|
+
if (contacts.telegram)
|
|
81
|
+
transports.add('telegram');
|
|
82
|
+
if (contacts.web)
|
|
83
|
+
transports.add('web');
|
|
84
|
+
if (contacts.webhook)
|
|
85
|
+
transports.add('webhook');
|
|
86
|
+
return Array.from(transports);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Resolve contact to address
|
|
90
|
+
*/
|
|
91
|
+
export function resolveAddress(contacts, channel) {
|
|
92
|
+
const contact = contacts[channel];
|
|
93
|
+
if (!contact)
|
|
94
|
+
return null;
|
|
95
|
+
const transport = channelToTransport(channel);
|
|
96
|
+
if (typeof contact === 'string') {
|
|
97
|
+
return { transport, value: contact };
|
|
98
|
+
}
|
|
99
|
+
// Handle structured contact types
|
|
100
|
+
switch (channel) {
|
|
101
|
+
case 'email':
|
|
102
|
+
const emailContact = contact;
|
|
103
|
+
return { transport, value: emailContact.address, name: emailContact.name };
|
|
104
|
+
case 'phone':
|
|
105
|
+
case 'sms':
|
|
106
|
+
case 'whatsapp':
|
|
107
|
+
const phoneContact = contact;
|
|
108
|
+
return { transport, value: phoneContact.number };
|
|
109
|
+
case 'slack':
|
|
110
|
+
const slackContact = contact;
|
|
111
|
+
return {
|
|
112
|
+
transport,
|
|
113
|
+
value: slackContact.user || slackContact.channel || '',
|
|
114
|
+
metadata: { workspace: slackContact.workspace },
|
|
115
|
+
};
|
|
116
|
+
case 'teams':
|
|
117
|
+
const teamsContact = contact;
|
|
118
|
+
return {
|
|
119
|
+
transport,
|
|
120
|
+
value: teamsContact.user || teamsContact.channel || '',
|
|
121
|
+
metadata: { team: teamsContact.team },
|
|
122
|
+
};
|
|
123
|
+
case 'discord':
|
|
124
|
+
const discordContact = contact;
|
|
125
|
+
return {
|
|
126
|
+
transport,
|
|
127
|
+
value: discordContact.user || discordContact.channel || '',
|
|
128
|
+
metadata: { server: discordContact.server },
|
|
129
|
+
};
|
|
130
|
+
case 'telegram':
|
|
131
|
+
const telegramContact = contact;
|
|
132
|
+
return { transport, value: telegramContact.user || telegramContact.chat || '' };
|
|
133
|
+
case 'web':
|
|
134
|
+
const webContact = contact;
|
|
135
|
+
return { transport, value: webContact.userId || '', metadata: { url: webContact.url } };
|
|
136
|
+
case 'webhook':
|
|
137
|
+
const webhookContact = contact;
|
|
138
|
+
return { transport, value: webhookContact.url, metadata: { secret: webhookContact.secret } };
|
|
139
|
+
case 'api':
|
|
140
|
+
const apiContact = contact;
|
|
141
|
+
return { transport, value: apiContact.endpoint, metadata: { auth: apiContact.auth } };
|
|
142
|
+
default:
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Resolve all addresses for a worker
|
|
148
|
+
*/
|
|
149
|
+
export function resolveWorkerAddresses(worker) {
|
|
150
|
+
const addresses = [];
|
|
151
|
+
const channels = [
|
|
152
|
+
'email', 'slack', 'teams', 'discord', 'phone', 'sms',
|
|
153
|
+
'whatsapp', 'telegram', 'web', 'api', 'webhook',
|
|
154
|
+
];
|
|
155
|
+
for (const channel of channels) {
|
|
156
|
+
const address = resolveAddress(worker.contacts, channel);
|
|
157
|
+
if (address)
|
|
158
|
+
addresses.push(address);
|
|
159
|
+
}
|
|
160
|
+
return addresses;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Get primary address for a worker based on preferences
|
|
164
|
+
*/
|
|
165
|
+
export function getPrimaryAddress(worker) {
|
|
166
|
+
const preferences = worker.preferences;
|
|
167
|
+
if (preferences?.primary) {
|
|
168
|
+
return resolveAddress(worker.contacts, preferences.primary);
|
|
169
|
+
}
|
|
170
|
+
// Default priority: slack > email > teams > sms > phone
|
|
171
|
+
const defaultPriority = ['slack', 'email', 'teams', 'sms', 'phone'];
|
|
172
|
+
for (const channel of defaultPriority) {
|
|
173
|
+
const address = resolveAddress(worker.contacts, channel);
|
|
174
|
+
if (address)
|
|
175
|
+
return address;
|
|
176
|
+
}
|
|
177
|
+
// Fall back to any available
|
|
178
|
+
const addresses = resolveWorkerAddresses(worker);
|
|
179
|
+
return addresses[0] || null;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Transport registry
|
|
183
|
+
*/
|
|
184
|
+
const transportRegistry = new Map();
|
|
185
|
+
/**
|
|
186
|
+
* Register a transport handler
|
|
187
|
+
*/
|
|
188
|
+
export function registerTransport(transport, handler) {
|
|
189
|
+
transportRegistry.set(transport, handler);
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Get transport handler
|
|
193
|
+
*/
|
|
194
|
+
export function getTransportHandler(transport) {
|
|
195
|
+
return transportRegistry.get(transport);
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Check if transport is registered
|
|
199
|
+
*/
|
|
200
|
+
export function hasTransport(transport) {
|
|
201
|
+
return transportRegistry.has(transport);
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* List registered transports
|
|
205
|
+
*/
|
|
206
|
+
export function listTransports() {
|
|
207
|
+
return Array.from(transportRegistry.keys());
|
|
208
|
+
}
|
|
209
|
+
// =============================================================================
|
|
210
|
+
// Default Transport Handlers (Stubs - implemented by providers)
|
|
211
|
+
// =============================================================================
|
|
212
|
+
/**
|
|
213
|
+
* Send via transport
|
|
214
|
+
*/
|
|
215
|
+
export async function sendViaTransport(transport, payload, config) {
|
|
216
|
+
const handler = transportRegistry.get(transport);
|
|
217
|
+
if (!handler) {
|
|
218
|
+
return {
|
|
219
|
+
success: false,
|
|
220
|
+
transport,
|
|
221
|
+
error: `Transport '${transport}' not registered`,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
try {
|
|
225
|
+
return await handler(payload, config || { transport });
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
return {
|
|
229
|
+
success: false,
|
|
230
|
+
transport,
|
|
231
|
+
error: error instanceof Error ? error.message : String(error),
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Send to multiple transports (fan-out)
|
|
237
|
+
*/
|
|
238
|
+
export async function sendToMultipleTransports(transports, payload, configs) {
|
|
239
|
+
const results = await Promise.all(transports.map(transport => sendViaTransport(transport, payload, configs?.[transport])));
|
|
240
|
+
return results;
|
|
241
|
+
}
|
|
242
|
+
// =============================================================================
|
|
243
|
+
// Worker Action to Transport
|
|
244
|
+
// =============================================================================
|
|
245
|
+
/**
|
|
246
|
+
* Build message payload from notify action
|
|
247
|
+
*/
|
|
248
|
+
export function buildNotifyPayload(action) {
|
|
249
|
+
return {
|
|
250
|
+
to: resolveActionTarget(action.object),
|
|
251
|
+
body: action.message,
|
|
252
|
+
type: 'notification',
|
|
253
|
+
priority: action.priority || 'normal',
|
|
254
|
+
metadata: action.metadata,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Build message payload from ask action
|
|
259
|
+
*/
|
|
260
|
+
export function buildAskPayload(action) {
|
|
261
|
+
return {
|
|
262
|
+
to: resolveActionTarget(action.object),
|
|
263
|
+
body: action.question,
|
|
264
|
+
type: 'question',
|
|
265
|
+
schema: action.schema,
|
|
266
|
+
timeout: action.timeout,
|
|
267
|
+
metadata: action.metadata,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Build message payload from approve action
|
|
272
|
+
*/
|
|
273
|
+
export function buildApprovePayload(action) {
|
|
274
|
+
return {
|
|
275
|
+
to: resolveActionTarget(action.object),
|
|
276
|
+
body: action.request,
|
|
277
|
+
type: 'approval',
|
|
278
|
+
timeout: action.timeout,
|
|
279
|
+
actions: [
|
|
280
|
+
{ id: 'approve', label: 'Approve', style: 'primary', value: true },
|
|
281
|
+
{ id: 'reject', label: 'Reject', style: 'danger', value: false },
|
|
282
|
+
],
|
|
283
|
+
metadata: {
|
|
284
|
+
...action.metadata,
|
|
285
|
+
context: action.context,
|
|
286
|
+
},
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Resolve action target to address string
|
|
291
|
+
*/
|
|
292
|
+
function resolveActionTarget(target) {
|
|
293
|
+
if (typeof target === 'string')
|
|
294
|
+
return target;
|
|
295
|
+
if ('contacts' in target) {
|
|
296
|
+
const address = getPrimaryAddress(target);
|
|
297
|
+
return address?.value || target.id;
|
|
298
|
+
}
|
|
299
|
+
return target.id;
|
|
300
|
+
}
|
|
301
|
+
// =============================================================================
|
|
302
|
+
// Integration with digital-tools Message/Call types
|
|
303
|
+
// =============================================================================
|
|
304
|
+
/**
|
|
305
|
+
* Message type mapping (from digital-tools)
|
|
306
|
+
*/
|
|
307
|
+
export const MessageTypeMapping = {
|
|
308
|
+
email: 'email',
|
|
309
|
+
sms: 'text',
|
|
310
|
+
slack: 'chat',
|
|
311
|
+
teams: 'chat',
|
|
312
|
+
discord: 'chat',
|
|
313
|
+
whatsapp: 'text',
|
|
314
|
+
telegram: 'text',
|
|
315
|
+
voice: 'voicemail', // For voicemail messages
|
|
316
|
+
};
|
|
317
|
+
/**
|
|
318
|
+
* Call type mapping (from digital-tools)
|
|
319
|
+
*/
|
|
320
|
+
export const CallTypeMapping = {
|
|
321
|
+
phone: 'phone',
|
|
322
|
+
voice: 'phone',
|
|
323
|
+
web: 'web',
|
|
324
|
+
video: 'video',
|
|
325
|
+
};
|
|
326
|
+
/**
|
|
327
|
+
* Convert worker notification to digital-tools Message format
|
|
328
|
+
*/
|
|
329
|
+
export function toDigitalToolsMessage(payload, transport) {
|
|
330
|
+
const messageType = MessageTypeMapping[transport] || 'chat';
|
|
331
|
+
return {
|
|
332
|
+
type: messageType,
|
|
333
|
+
to: Array.isArray(payload.to) ? payload.to : [payload.to],
|
|
334
|
+
from: payload.from,
|
|
335
|
+
subject: payload.subject,
|
|
336
|
+
body: payload.body,
|
|
337
|
+
html: payload.html,
|
|
338
|
+
metadata: {
|
|
339
|
+
...payload.metadata,
|
|
340
|
+
workerActionType: payload.type,
|
|
341
|
+
priority: payload.priority,
|
|
342
|
+
},
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Convert digital-tools Message to worker notification format
|
|
347
|
+
*/
|
|
348
|
+
export function fromDigitalToolsMessage(message) {
|
|
349
|
+
return {
|
|
350
|
+
to: message.to,
|
|
351
|
+
from: message.from,
|
|
352
|
+
subject: message.subject,
|
|
353
|
+
body: message.body,
|
|
354
|
+
html: message.html,
|
|
355
|
+
metadata: message.metadata,
|
|
356
|
+
};
|
|
357
|
+
}
|
package/src/types.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for digital-workers
|
|
3
|
+
*
|
|
4
|
+
* Digital workers (Agents and Humans) communicate through Actions that integrate
|
|
5
|
+
* with the ai-workflows system. Worker actions (notify, ask, approve, decide)
|
|
6
|
+
* are durable workflow actions with Actor/Object semantics.
|
|
7
|
+
*
|
|
8
|
+
* ## Key Concepts
|
|
9
|
+
*
|
|
10
|
+
* - **Worker**: Common interface for Agent and Human
|
|
11
|
+
* - **Contacts**: How a worker can be reached (email, slack, phone, etc.)
|
|
12
|
+
* - **Action**: Durable workflow action (notify, ask, approve, decide)
|
|
13
|
+
* - **Team**: Group of workers with shared contacts
|
|
14
|
+
*
|
|
15
|
+
* @packageDocumentation
|
|
16
|
+
*/
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// Worker Verbs - Following ai-database Verb pattern
|
|
19
|
+
// ============================================================================
|
|
20
|
+
/**
|
|
21
|
+
* Worker verbs following the ai-database conjugation pattern
|
|
22
|
+
*
|
|
23
|
+
* Each verb has:
|
|
24
|
+
* - action: Base form (notify, ask, approve, decide)
|
|
25
|
+
* - actor: Who does it (notifier, asker, approver, decider)
|
|
26
|
+
* - activity: Gerund (notifying, asking, approving, deciding)
|
|
27
|
+
* - reverse: Past forms (notifiedAt, notifiedBy, askedAt, etc.)
|
|
28
|
+
*/
|
|
29
|
+
export const WorkerVerbs = {
|
|
30
|
+
notify: {
|
|
31
|
+
action: 'notify',
|
|
32
|
+
actor: 'notifier',
|
|
33
|
+
act: 'notifies',
|
|
34
|
+
activity: 'notifying',
|
|
35
|
+
result: 'notification',
|
|
36
|
+
reverse: { at: 'notifiedAt', by: 'notifiedBy', via: 'notifiedVia' },
|
|
37
|
+
},
|
|
38
|
+
ask: {
|
|
39
|
+
action: 'ask',
|
|
40
|
+
actor: 'asker',
|
|
41
|
+
act: 'asks',
|
|
42
|
+
activity: 'asking',
|
|
43
|
+
result: 'question',
|
|
44
|
+
reverse: { at: 'askedAt', by: 'askedBy', via: 'askedVia' },
|
|
45
|
+
},
|
|
46
|
+
approve: {
|
|
47
|
+
action: 'approve',
|
|
48
|
+
actor: 'approver',
|
|
49
|
+
act: 'approves',
|
|
50
|
+
activity: 'approving',
|
|
51
|
+
result: 'approval',
|
|
52
|
+
reverse: { at: 'approvedAt', by: 'approvedBy', via: 'approvedVia' },
|
|
53
|
+
inverse: 'reject',
|
|
54
|
+
},
|
|
55
|
+
decide: {
|
|
56
|
+
action: 'decide',
|
|
57
|
+
actor: 'decider',
|
|
58
|
+
act: 'decides',
|
|
59
|
+
activity: 'deciding',
|
|
60
|
+
result: 'decision',
|
|
61
|
+
reverse: { at: 'decidedAt', by: 'decidedBy' },
|
|
62
|
+
},
|
|
63
|
+
do: {
|
|
64
|
+
action: 'do',
|
|
65
|
+
actor: 'doer',
|
|
66
|
+
act: 'does',
|
|
67
|
+
activity: 'doing',
|
|
68
|
+
result: 'task',
|
|
69
|
+
reverse: { at: 'doneAt', by: 'doneBy' },
|
|
70
|
+
},
|
|
71
|
+
};
|