@opencxh/domain 1.254.0 → 1.256.0
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/dist/entities/activity/types.d.ts +25 -1
- package/dist/entities/agent/index.d.ts +1 -0
- package/dist/entities/agent/types.d.ts +55 -0
- package/dist/entities/ai-profile/types.d.ts +14 -22
- package/dist/entities/assignment/types.d.ts +37 -0
- package/dist/entities/playbook/trigger-vars.d.ts +11 -0
- package/dist/entities/playbook/trigger-vars.test.d.ts +1 -0
- package/dist/entities/playbook/types.d.ts +11 -0
- package/dist/entities/user/types.d.ts +4 -11
- package/dist/index.cjs +16 -16
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1070 -1034
- package/package.json +1 -1
|
@@ -5,7 +5,7 @@ import { ReactionSummary } from '../reaction/types';
|
|
|
5
5
|
export type CallStatus = "new" | "connecting" | "ringing" | "connected" | "held" | "ended" | "failed";
|
|
6
6
|
export type CallDirection = "inbound" | "outbound";
|
|
7
7
|
export type CallType = "audio" | "video" | "data" | "screen-share";
|
|
8
|
-
export type ActivityType = "VOICE_CALL_STARTED" | "VOICE_CALL_ANSWERED" | "VOICE_CALL_HOLD" | "VOICE_CALL_UNHOLD" | "VOICE_CALL_ENDED" | "VOICE_CALL_MISSED" | "VOICE_CALL_VOICEMAIL" | "VIDEO_CALL_STARTED" | "VIDEO_CALL_ANSWERED" | "VIDEO_CALL_HOLD" | "VIDEO_CALL_UNHOLD" | "VIDEO_CALL_ENDED" | "VIDEO_CALL_MISSED" | "EMAIL_RECEIVED" | "EMAIL_SENT" | "CHAT_MESSAGE_SENT" | "CHAT_MESSAGE_RECEIVED" | "CHAT_MEMBER_JOINED" | "CHAT_MEMBER_LEFT" | "CHAT_RENAMED" | "CHAT_CALL_STARTED" | "CHAT_CALL_ENDED" | "CHAT_EVENT" | "AI_MESSAGE_ADDED" | "AI_ACTION_PROPOSED" | "PLAYBOOK_STARTED" | "PLAYBOOK_COMPLETED" | "PLAYBOOK_ESCALATED" | "MEETING_SCHEDULED" | "MEETING_STARTED" | "MEETING_ENDED" | "MEETING_PARTICIPANT_JOINED" | "MEETING_PARTICIPANT_LEFT" | "COMMENT_ADDED" | "FILE_UPLOADED" | "INTERACTION_CREATED" | "INTERACTION_STATUS_CHANGED" | "INTERACTION_ASSIGNED" | "SLA_BREACHED" | "VOICE_CALL_FAILED" | "VIDEO_CALL_FAILED" | "TRANSCRIPT_ADDED" | "WORK_COMMENT_ADDED" | "WORK_ITEM_ASSIGNED" | "WORK_ITEM_STATUS_CHANGED";
|
|
8
|
+
export type ActivityType = "VOICE_CALL_STARTED" | "VOICE_CALL_ANSWERED" | "VOICE_CALL_HOLD" | "VOICE_CALL_UNHOLD" | "VOICE_CALL_ENDED" | "VOICE_CALL_MISSED" | "VOICE_CALL_VOICEMAIL" | "VIDEO_CALL_STARTED" | "VIDEO_CALL_ANSWERED" | "VIDEO_CALL_HOLD" | "VIDEO_CALL_UNHOLD" | "VIDEO_CALL_ENDED" | "VIDEO_CALL_MISSED" | "EMAIL_RECEIVED" | "EMAIL_SENT" | "CHAT_MESSAGE_SENT" | "CHAT_MESSAGE_RECEIVED" | "CHAT_MEMBER_JOINED" | "CHAT_MEMBER_LEFT" | "CHAT_RENAMED" | "CHAT_CALL_STARTED" | "CHAT_CALL_ENDED" | "CHAT_EVENT" | "AI_MESSAGE_ADDED" | "AI_ACTION_PROPOSED" | "PLAYBOOK_STARTED" | "PLAYBOOK_COMPLETED" | "PLAYBOOK_ESCALATED" | "AGENT_DELEGATED" | "AGENT_ANSWERED" | "MEETING_SCHEDULED" | "MEETING_STARTED" | "MEETING_ENDED" | "MEETING_PARTICIPANT_JOINED" | "MEETING_PARTICIPANT_LEFT" | "COMMENT_ADDED" | "FILE_UPLOADED" | "INTERACTION_CREATED" | "INTERACTION_STATUS_CHANGED" | "INTERACTION_ASSIGNED" | "SLA_BREACHED" | "VOICE_CALL_FAILED" | "VIDEO_CALL_FAILED" | "TRANSCRIPT_ADDED" | "WORK_COMMENT_ADDED" | "WORK_ITEM_ASSIGNED" | "WORK_ITEM_STATUS_CHANGED";
|
|
9
9
|
export interface Attachment {
|
|
10
10
|
id: string;
|
|
11
11
|
filename: string;
|
|
@@ -299,6 +299,24 @@ export type PlaybookLifecyclePayload = {
|
|
|
299
299
|
/** Who the run acted for; saves the UI from fetching the playbook again. */
|
|
300
300
|
actor?: ActingIdentity;
|
|
301
301
|
};
|
|
302
|
+
/**
|
|
303
|
+
* One agent asking another, and the answer coming back — the two markers that make delegation
|
|
304
|
+
* visible on the timeline instead of only inside a second thread.
|
|
305
|
+
*
|
|
306
|
+
* Extends the playbook lifecycle payload rather than standing beside it, because these *are*
|
|
307
|
+
* playbook-authored markers: they carry the same `runId` that damps re-triggering, and every
|
|
308
|
+
* reader that already handles a lifecycle marker handles these unchanged.
|
|
309
|
+
*/
|
|
310
|
+
export type AgentDelegationPayload = PlaybookLifecyclePayload & {
|
|
311
|
+
/** The agent that asked. */
|
|
312
|
+
fromAgentName: string;
|
|
313
|
+
/** The agent that was asked. Absent on the answer, where `fromAgentName` is the answerer. */
|
|
314
|
+
toAgentName?: string;
|
|
315
|
+
/** The sub-question, as the delegating agent wrote it. Trimmed for the timeline. */
|
|
316
|
+
ask?: string;
|
|
317
|
+
/** The delegated assignment, so the UI can link to its thread. */
|
|
318
|
+
assignmentId?: string;
|
|
319
|
+
};
|
|
302
320
|
export type Activity = (BaseActivity & {
|
|
303
321
|
type: "VOICE_CALL_STARTED";
|
|
304
322
|
payload: VoiceCallPayload;
|
|
@@ -392,6 +410,12 @@ export type Activity = (BaseActivity & {
|
|
|
392
410
|
}) | (BaseActivity & {
|
|
393
411
|
type: "PLAYBOOK_ESCALATED";
|
|
394
412
|
payload: PlaybookLifecyclePayload;
|
|
413
|
+
}) | (BaseActivity & {
|
|
414
|
+
type: "AGENT_DELEGATED";
|
|
415
|
+
payload: AgentDelegationPayload;
|
|
416
|
+
}) | (BaseActivity & {
|
|
417
|
+
type: "AGENT_ANSWERED";
|
|
418
|
+
payload: AgentDelegationPayload;
|
|
395
419
|
}) | (BaseActivity & {
|
|
396
420
|
type: "MEETING_SCHEDULED";
|
|
397
421
|
payload: MeetingPayload;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './types';
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An **agent**: a digital colleague. A profile plus an identity plus a mandate.
|
|
3
|
+
*
|
|
4
|
+
* **Why this is its own record, next to the user row and the profile.** Those two already
|
|
5
|
+
* existed and pointed at each other: `User.profileId` said which brain, `Playbook.agentId` said
|
|
6
|
+
* which agent, and nothing related them — `agentId: Alice` running on Bob's brain was a valid
|
|
7
|
+
* configuration, and did happen (see `agent/brain.ts` in the ai app). One record ends that.
|
|
8
|
+
*
|
|
9
|
+
* **`id` is the user id.** Not a `userId` column beside a generated key: an agent *is* that user
|
|
10
|
+
* (that is what gives it a real identity — fail-closed authorization, its own name on the
|
|
11
|
+
* timeline, team membership, its own budget), so a second identifier would only be a second thing
|
|
12
|
+
* that can disagree. Every `agentId` in this codebase is therefore also this row's id.
|
|
13
|
+
*
|
|
14
|
+
* **What is deliberately not here.** The name lives on the user row, because that is what every
|
|
15
|
+
* timeline, mention list and assignee picker already reads; duplicating it would let two places
|
|
16
|
+
* disagree about what an agent is called.
|
|
17
|
+
*/
|
|
18
|
+
export interface Agent {
|
|
19
|
+
/** The agent's user id. See above: there is no second identifier. */
|
|
20
|
+
id: string;
|
|
21
|
+
organizationId: string;
|
|
22
|
+
/** The brain: the `AIProfile` this agent thinks with. */
|
|
23
|
+
profileId: string;
|
|
24
|
+
/**
|
|
25
|
+
* What this agent is for, in one line.
|
|
26
|
+
*
|
|
27
|
+
* **On the agent, not on the profile.** Another agent reads this line to decide whether to call
|
|
28
|
+
* this one in ({@link Agent.delegateTo}), so it has to describe *this colleague* — and two
|
|
29
|
+
* agents sharing the Support profile are two different colleagues. It used to be
|
|
30
|
+
* `AIProfile.description`, which gave them the same blurb.
|
|
31
|
+
*/
|
|
32
|
+
description?: string;
|
|
33
|
+
/**
|
|
34
|
+
* Per-tool confirmation policy: which of this agent's tools count as a write.
|
|
35
|
+
*
|
|
36
|
+
* Moved off the profile because it is a mandate, not expertise: the same Support brain may be
|
|
37
|
+
* trusted to send in one agent's hands and only to propose in another's. Under autonomy
|
|
38
|
+
* "suggest" write tools are held for approval; reads run freely. A tool absent from this list
|
|
39
|
+
* defaults to "write" (safe).
|
|
40
|
+
*/
|
|
41
|
+
toolPolicy?: {
|
|
42
|
+
name: string;
|
|
43
|
+
policy: "read" | "write";
|
|
44
|
+
}[];
|
|
45
|
+
/**
|
|
46
|
+
* Which colleagues this agent may call in for a sub-question (agent ids).
|
|
47
|
+
*
|
|
48
|
+
* **Opt-in**: absent and `[]` both mean none. Who you may wake is a permission, and a default
|
|
49
|
+
* of "everyone" would make every new agent reachable by every other one the moment it exists.
|
|
50
|
+
*
|
|
51
|
+
* Moved off the profile for the reason the whole record exists: this is a relation *between
|
|
52
|
+
* agents*, and storing it on a shared brain gave two agents one delegate list.
|
|
53
|
+
*/
|
|
54
|
+
delegateTo?: string[];
|
|
55
|
+
}
|
|
@@ -27,32 +27,24 @@ export interface AIProfile<T extends Record<string, any> = Record<string, any>>
|
|
|
27
27
|
systemPrompt?: string;
|
|
28
28
|
contextIds?: string[];
|
|
29
29
|
predefinedPrompts?: PredefinedPrompt[];
|
|
30
|
-
/** Namespaced tool names this profile may use (default: none enabled). */
|
|
31
|
-
enabledTools?: string[];
|
|
32
30
|
/**
|
|
33
|
-
*
|
|
34
|
-
* rows with a form on top, and a migration to a map would only yield a nicer shape;
|
|
35
|
-
* `enabledTools` next to it is a list too.
|
|
36
|
-
*
|
|
37
|
-
* Under autonomy "suggest", write tools are held for approval; reads run freely. A tool absent
|
|
38
|
-
* from this list defaults to "write" (safe).
|
|
39
|
-
*
|
|
40
|
-
* **Where this does and does not apply.** Enforced in `playbook/ai/gate.ts`, and thereby on
|
|
41
|
-
* every autonomous run (workflow or procedure). The interactive assistant does **nothing** with
|
|
42
|
-
* it: there is no confirmation step there. So a `write` marking you expect to enforce in a chat
|
|
43
|
-
* conversation does nothing.
|
|
31
|
+
* Namespaced tool names this profile may use (default: none enabled).
|
|
44
32
|
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
33
|
+
* Expertise, not mandate: *what this brain knows how to do*. Whether a given agent is trusted
|
|
34
|
+
* to actually use a write tool is `Agent.toolPolicy`, and which colleagues it may call in is
|
|
35
|
+
* `Agent.delegateTo` — both moved off this record, because two agents can share one brain and
|
|
36
|
+
* still not be trusted with the same things.
|
|
47
37
|
*/
|
|
48
|
-
|
|
49
|
-
name: string;
|
|
50
|
-
policy: "read" | "write";
|
|
51
|
-
}[];
|
|
38
|
+
enabledTools?: string[];
|
|
52
39
|
/**
|
|
53
|
-
* Whether the acting user's PERSONAL MCP tools (their own per-user connections)
|
|
54
|
-
*
|
|
55
|
-
*
|
|
40
|
+
* Whether the acting user's PERSONAL MCP tools (their own per-user connections) are available
|
|
41
|
+
* on top of the admin-curated `enabledTools`. "off" (default) = strictly the curated set.
|
|
42
|
+
*
|
|
43
|
+
* **Stays on the profile while `toolPolicy` and `delegateTo` moved to `Agent`**, because this
|
|
44
|
+
* one is only ever true of the interactive assistant: there a person is typing and the tools
|
|
45
|
+
* are their own. An autonomous run has no watching user, so borrowing someone's token is never
|
|
46
|
+
* the intent — `resolveToolset` passes `SYSTEM_TOOL_POLICY` and this field is dead on that
|
|
47
|
+
* path. Putting it on the agent would have made it a setting that cannot do anything.
|
|
56
48
|
*/
|
|
57
49
|
personalTools?: "off" | "allow";
|
|
58
50
|
}
|
|
@@ -79,6 +79,20 @@ export interface Assignment {
|
|
|
79
79
|
* triggered one may not. The list needs to tell them apart, so it is stored.
|
|
80
80
|
*/
|
|
81
81
|
origin?: "trigger" | "user";
|
|
82
|
+
/**
|
|
83
|
+
* The assignment that delegated this one, when an agent asked a colleague-agent a sub-question.
|
|
84
|
+
* The return address: `applyTurn` reports back here the moment this assignment goes terminal.
|
|
85
|
+
*/
|
|
86
|
+
delegatedFrom?: string;
|
|
87
|
+
/**
|
|
88
|
+
* The agent ids from the root of the delegation chain down to and including this assignment's
|
|
89
|
+
* own agent.
|
|
90
|
+
*
|
|
91
|
+
* **One field, two gates.** Its length is the depth and `includes()` is the cycle check — see
|
|
92
|
+
* {@link delegationRefusal}. A separate `depth` counter beside a chain would be a second source
|
|
93
|
+
* of truth for the same fact.
|
|
94
|
+
*/
|
|
95
|
+
delegationChain?: string[];
|
|
82
96
|
status: AssignmentStatus;
|
|
83
97
|
/**
|
|
84
98
|
* What the **assignment** is waiting on between two turns.
|
|
@@ -223,3 +237,26 @@ export declare function decideAssignmentState(control: AssignmentControl | undef
|
|
|
223
237
|
turns: number;
|
|
224
238
|
reason?: string;
|
|
225
239
|
};
|
|
240
|
+
/**
|
|
241
|
+
* How many **hops** one delegation chain may make. A → B is one hop, A → B → C is two.
|
|
242
|
+
*
|
|
243
|
+
* Two, not more: "ask a colleague who asks their colleague" is the last chain that still has an
|
|
244
|
+
* owner anyone can name. Every extra level multiplies the turn ceiling by twelve for work nobody
|
|
245
|
+
* asked for.
|
|
246
|
+
*/
|
|
247
|
+
export declare const MAX_DELEGATION_DEPTH = 2;
|
|
248
|
+
/**
|
|
249
|
+
* Why this agent may **not** delegate to that one — or `null` when it may.
|
|
250
|
+
*
|
|
251
|
+
* Pure, so the rule is testable without a Bridge, and the same shape as `decideAssignmentState`
|
|
252
|
+
* beside it.
|
|
253
|
+
*
|
|
254
|
+
* **A reason, not a boolean.** The string goes back to the model as the tool's error, so it can
|
|
255
|
+
* pick another route instead of retrying the same refused call. `toolsOutsideBrain` in the ai app
|
|
256
|
+
* does the same for the same reason.
|
|
257
|
+
*
|
|
258
|
+
* @param chain The calling assignment's {@link Assignment.delegationChain}; absent = root.
|
|
259
|
+
* @param allowed The calling agent's `delegateTo`. `undefined` and `[]` both mean **none**:
|
|
260
|
+
* delegation is opt-in, unlike `enabledTools` where absent means "no filter".
|
|
261
|
+
*/
|
|
262
|
+
export declare function delegationRefusal(chain: string[] | undefined, targetAgentId: string, callerAgentId: string, allowed: string[] | undefined): string | null;
|
|
@@ -45,12 +45,15 @@ export declare const TRIGGER_VARS: readonly [{
|
|
|
45
45
|
}, {
|
|
46
46
|
readonly name: "fromStatus";
|
|
47
47
|
readonly type: "string";
|
|
48
|
+
readonly onlyFor: readonly ["INTERACTION_STATUS_CHANGED"];
|
|
48
49
|
}, {
|
|
49
50
|
readonly name: "toStatus";
|
|
50
51
|
readonly type: "string";
|
|
52
|
+
readonly onlyFor: readonly ["INTERACTION_STATUS_CHANGED"];
|
|
51
53
|
}, {
|
|
52
54
|
readonly name: "toCategory";
|
|
53
55
|
readonly type: "string";
|
|
56
|
+
readonly onlyFor: readonly ["WORK_ITEM_STATUS_CHANGED"];
|
|
54
57
|
}, {
|
|
55
58
|
readonly name: "contactId";
|
|
56
59
|
readonly type: "string";
|
|
@@ -70,6 +73,14 @@ export declare const TRIGGER_VARS: readonly [{
|
|
|
70
73
|
readonly name: "assigneeUserId";
|
|
71
74
|
readonly type: "string";
|
|
72
75
|
}];
|
|
76
|
+
/**
|
|
77
|
+
* Does this trigger fill that variable? A trigger listing no activity types matches
|
|
78
|
+
* everything, which is also what the engine does with it.
|
|
79
|
+
*/
|
|
80
|
+
export declare function triggerFillsVar(variable: {
|
|
81
|
+
name: string;
|
|
82
|
+
onlyFor?: readonly string[];
|
|
83
|
+
}, activityTypes: readonly string[] | undefined): boolean;
|
|
73
84
|
/**
|
|
74
85
|
* What a classify step **yields** per mode — the contract `runClassify` honours.
|
|
75
86
|
*
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -355,6 +355,17 @@ export type WaitingOn = {
|
|
|
355
355
|
} | {
|
|
356
356
|
on: "task";
|
|
357
357
|
taskId: string;
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Another agent is working on a sub-question; its terminal status is the signal.
|
|
361
|
+
*
|
|
362
|
+
* Deliberately quiet: `wakesAssignment` rule 5 only lets a plain internal note through while
|
|
363
|
+
* waiting on a `"reply"`, so an assignment parked on an agent is woken by that agent's answer
|
|
364
|
+
* or by an explicit mention — not by every colleague passing by.
|
|
365
|
+
*/
|
|
366
|
+
| {
|
|
367
|
+
on: "agent";
|
|
368
|
+
assignmentId: string;
|
|
358
369
|
};
|
|
359
370
|
/** Terminal states: a run does not leave these on its own. */
|
|
360
371
|
export declare const TERMINAL_RUN_STATUSES: readonly ["done", "escalated", "failed", "stopped"];
|
|
@@ -9,6 +9,10 @@
|
|
|
9
9
|
* audit loses it.
|
|
10
10
|
*
|
|
11
11
|
* Absent means `"user"`: existing rows are humans, and the mirror is not backfilled.
|
|
12
|
+
*
|
|
13
|
+
* This flag is all the user record knows about being an agent. The brain and the mandate live on
|
|
14
|
+
* `Agent` in the ai app, keyed by this user's id — the ai configuration used to sit here as
|
|
15
|
+
* `profileId`, which made user management carry agent behaviour and gave one fact two homes.
|
|
12
16
|
*/
|
|
13
17
|
export type UserType = "user" | "agent";
|
|
14
18
|
/**
|
|
@@ -27,15 +31,6 @@ export interface UserProfile {
|
|
|
27
31
|
displayName?: string;
|
|
28
32
|
avatarUrl?: string;
|
|
29
33
|
type?: UserType;
|
|
30
|
-
/**
|
|
31
|
-
* An agent's brain, when this is an agent — see {@link User.profileId}.
|
|
32
|
-
*
|
|
33
|
-
* Belongs on this compact record and not only on the full one: "is this an agent" is already
|
|
34
|
-
* here (`type`), and "with which brain" is the immediate follow-up of every caller handing it
|
|
35
|
-
* work. Without this field the ai app had to use the admin route — the whole record plus an RPC
|
|
36
|
-
* to `system.rbac` — to learn one id.
|
|
37
|
-
*/
|
|
38
|
-
profileId?: string;
|
|
39
34
|
}
|
|
40
35
|
/** Is this user an agent? An absent type = human. */
|
|
41
36
|
export declare function isAgentUser(user: {
|
|
@@ -53,8 +48,6 @@ export interface User {
|
|
|
53
48
|
* not own it. Everything that needs to know "is this a human" reads this field.
|
|
54
49
|
*/
|
|
55
50
|
type?: UserType;
|
|
56
|
-
/** An agent's brain: the `AIProfile` it works with. Only set when `type === "agent"`. */
|
|
57
|
-
profileId?: string;
|
|
58
51
|
/** Mirrored from system.users. */
|
|
59
52
|
firstName?: string;
|
|
60
53
|
lastName?: string;
|