@ddse/acm-examples 0.5.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/LICENSE +21 -0
- package/README.md +113 -0
- package/bin/acm-demo.ts +495 -0
- package/data/coaching/agents.json +16 -0
- package/data/coaching/transcripts.json +37 -0
- package/data/documents.json +72 -0
- package/data/entitlement/customers.json +38 -0
- package/data/entitlement/policies.json +38 -0
- package/data/incidents/incidents.json +30 -0
- package/data/incidents/routing_rules.json +36 -0
- package/data/invoices/invoices.json +38 -0
- package/data/invoices/purchase-orders.json +38 -0
- package/data/issues.json +99 -0
- package/data/knowledge/docs/kb-001.md +7 -0
- package/data/knowledge/docs/kb-002.md +7 -0
- package/data/knowledge/docs/kb-003.md +9 -0
- package/data/knowledge/index.json +25 -0
- package/data/orders.json +106 -0
- package/dist/bin/acm-demo.d.ts +3 -0
- package/dist/bin/acm-demo.d.ts.map +1 -0
- package/dist/bin/acm-demo.js +392 -0
- package/dist/bin/acm-demo.js.map +1 -0
- package/dist/src/context/directives.d.ts +3 -0
- package/dist/src/context/directives.d.ts.map +1 -0
- package/dist/src/context/directives.js +325 -0
- package/dist/src/context/directives.js.map +1 -0
- package/dist/src/context/index.d.ts +2 -0
- package/dist/src/context/index.d.ts.map +1 -0
- package/dist/src/context/index.js +2 -0
- package/dist/src/context/index.js.map +1 -0
- package/dist/src/data/coaching.d.ts +19 -0
- package/dist/src/data/coaching.d.ts.map +1 -0
- package/dist/src/data/coaching.js +22 -0
- package/dist/src/data/coaching.js.map +1 -0
- package/dist/src/data/entitlement.d.ts +25 -0
- package/dist/src/data/entitlement.d.ts.map +1 -0
- package/dist/src/data/entitlement.js +26 -0
- package/dist/src/data/entitlement.js.map +1 -0
- package/dist/src/data/incidents.d.ts +23 -0
- package/dist/src/data/incidents.d.ts.map +1 -0
- package/dist/src/data/incidents.js +37 -0
- package/dist/src/data/incidents.js.map +1 -0
- package/dist/src/data/invoices.d.ts +34 -0
- package/dist/src/data/invoices.d.ts.map +1 -0
- package/dist/src/data/invoices.js +49 -0
- package/dist/src/data/invoices.js.map +1 -0
- package/dist/src/data/knowledge.d.ts +11 -0
- package/dist/src/data/knowledge.d.ts.map +1 -0
- package/dist/src/data/knowledge.js +57 -0
- package/dist/src/data/knowledge.js.map +1 -0
- package/dist/src/data/loader.d.ts +4 -0
- package/dist/src/data/loader.d.ts.map +1 -0
- package/dist/src/data/loader.js +69 -0
- package/dist/src/data/loader.js.map +1 -0
- package/dist/src/examples/scenarios.d.ts +23 -0
- package/dist/src/examples/scenarios.d.ts.map +1 -0
- package/dist/src/examples/scenarios.js +609 -0
- package/dist/src/examples/scenarios.js.map +1 -0
- package/dist/src/goals/index.d.ts +8 -0
- package/dist/src/goals/index.d.ts.map +1 -0
- package/dist/src/goals/index.js +12 -0
- package/dist/src/goals/index.js.map +1 -0
- package/dist/src/policy.d.ts +5 -0
- package/dist/src/policy.d.ts.map +1 -0
- package/dist/src/policy.js +24 -0
- package/dist/src/policy.js.map +1 -0
- package/dist/src/registries.d.ts +18 -0
- package/dist/src/registries.d.ts.map +1 -0
- package/dist/src/registries.js +38 -0
- package/dist/src/registries.js.map +1 -0
- package/dist/src/renderer.d.ts +9 -0
- package/dist/src/renderer.d.ts.map +1 -0
- package/dist/src/renderer.js +76 -0
- package/dist/src/renderer.js.map +1 -0
- package/dist/src/search/bm25.d.ts +68 -0
- package/dist/src/search/bm25.d.ts.map +1 -0
- package/dist/src/search/bm25.js +131 -0
- package/dist/src/search/bm25.js.map +1 -0
- package/dist/src/search/index.d.ts +2 -0
- package/dist/src/search/index.d.ts.map +1 -0
- package/dist/src/search/index.js +3 -0
- package/dist/src/search/index.js.map +1 -0
- package/dist/src/tasks/coaching.d.ts +30 -0
- package/dist/src/tasks/coaching.d.ts.map +1 -0
- package/dist/src/tasks/coaching.js +143 -0
- package/dist/src/tasks/coaching.js.map +1 -0
- package/dist/src/tasks/entitlement.d.ts +29 -0
- package/dist/src/tasks/entitlement.d.ts.map +1 -0
- package/dist/src/tasks/entitlement.js +135 -0
- package/dist/src/tasks/entitlement.js.map +1 -0
- package/dist/src/tasks/incidents.d.ts +42 -0
- package/dist/src/tasks/incidents.d.ts.map +1 -0
- package/dist/src/tasks/incidents.js +189 -0
- package/dist/src/tasks/incidents.js.map +1 -0
- package/dist/src/tasks/index.d.ts +7 -0
- package/dist/src/tasks/index.d.ts.map +1 -0
- package/dist/src/tasks/index.js +7 -0
- package/dist/src/tasks/index.js.map +1 -0
- package/dist/src/tasks/invoices.d.ts +40 -0
- package/dist/src/tasks/invoices.d.ts.map +1 -0
- package/dist/src/tasks/invoices.js +180 -0
- package/dist/src/tasks/invoices.js.map +1 -0
- package/dist/src/tasks/knowledge.d.ts +23 -0
- package/dist/src/tasks/knowledge.d.ts.map +1 -0
- package/dist/src/tasks/knowledge.js +115 -0
- package/dist/src/tasks/knowledge.js.map +1 -0
- package/dist/src/tasks/legacy.d.ts +50 -0
- package/dist/src/tasks/legacy.d.ts.map +1 -0
- package/dist/src/tasks/legacy.js +85 -0
- package/dist/src/tasks/legacy.js.map +1 -0
- package/dist/src/tools/coaching/index.d.ts +49 -0
- package/dist/src/tools/coaching/index.d.ts.map +1 -0
- package/dist/src/tools/coaching/index.js +119 -0
- package/dist/src/tools/coaching/index.js.map +1 -0
- package/dist/src/tools/entitlement/index.d.ts +52 -0
- package/dist/src/tools/entitlement/index.d.ts.map +1 -0
- package/dist/src/tools/entitlement/index.js +120 -0
- package/dist/src/tools/entitlement/index.js.map +1 -0
- package/dist/src/tools/incidents/index.d.ts +55 -0
- package/dist/src/tools/incidents/index.d.ts.map +1 -0
- package/dist/src/tools/incidents/index.js +109 -0
- package/dist/src/tools/incidents/index.js.map +1 -0
- package/dist/src/tools/index.d.ts +90 -0
- package/dist/src/tools/index.d.ts.map +1 -0
- package/dist/src/tools/index.js +109 -0
- package/dist/src/tools/index.js.map +1 -0
- package/dist/src/tools/invoices/index.d.ts +56 -0
- package/dist/src/tools/invoices/index.d.ts.map +1 -0
- package/dist/src/tools/invoices/index.js +85 -0
- package/dist/src/tools/invoices/index.js.map +1 -0
- package/dist/src/tools/knowledge/index.d.ts +52 -0
- package/dist/src/tools/knowledge/index.d.ts.map +1 -0
- package/dist/src/tools/knowledge/index.js +120 -0
- package/dist/src/tools/knowledge/index.js.map +1 -0
- package/dist/tests/bm25.test.d.ts +2 -0
- package/dist/tests/bm25.test.d.ts.map +1 -0
- package/dist/tests/bm25.test.js +98 -0
- package/dist/tests/bm25.test.js.map +1 -0
- package/dist/tests/integration.test.d.ts +2 -0
- package/dist/tests/integration.test.d.ts.map +1 -0
- package/dist/tests/integration.test.js +126 -0
- package/dist/tests/integration.test.js.map +1 -0
- package/dist/tests/plan-hydration.test.d.ts +2 -0
- package/dist/tests/plan-hydration.test.d.ts.map +1 -0
- package/dist/tests/plan-hydration.test.js +28 -0
- package/dist/tests/plan-hydration.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/docs/examples-architecture.md +144 -0
- package/docs/successrun.md +1022 -0
- package/package.json +33 -0
- package/src/context/directives.ts +366 -0
- package/src/context/index.ts +1 -0
- package/src/data/coaching.ts +50 -0
- package/src/data/entitlement.ts +60 -0
- package/src/data/incidents.ts +78 -0
- package/src/data/invoices.ts +103 -0
- package/src/data/knowledge.ts +77 -0
- package/src/data/loader.ts +80 -0
- package/src/examples/scenarios.ts +724 -0
- package/src/goals/index.ts +18 -0
- package/src/policy.ts +30 -0
- package/src/registries.ts +48 -0
- package/src/renderer.ts +82 -0
- package/src/search/bm25.ts +173 -0
- package/src/search/index.ts +2 -0
- package/src/tasks/coaching.ts +217 -0
- package/src/tasks/entitlement.ts +197 -0
- package/src/tasks/incidents.ts +277 -0
- package/src/tasks/index.ts +6 -0
- package/src/tasks/invoices.ts +269 -0
- package/src/tasks/knowledge.ts +169 -0
- package/src/tasks/legacy.ts +112 -0
- package/src/tools/coaching/index.ts +197 -0
- package/src/tools/entitlement/index.ts +199 -0
- package/src/tools/incidents/index.ts +185 -0
- package/src/tools/index.ts +192 -0
- package/src/tools/invoices/index.ts +165 -0
- package/src/tools/knowledge/index.ts +203 -0
- package/tests/bm25.test.ts +129 -0
- package/tests/integration.test.ts +163 -0
- package/tests/plan-hydration.test.ts +33 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { Task, type RunContext } from '@ddse/acm-sdk';
|
|
2
|
+
import type { CustomerProfile } from '../data/entitlement.js';
|
|
3
|
+
import {
|
|
4
|
+
type FetchCustomerProfileInput,
|
|
5
|
+
type FetchCustomerProfileOutput,
|
|
6
|
+
type EvaluateEntitlementInput,
|
|
7
|
+
type EvaluateEntitlementOutput,
|
|
8
|
+
type NotifySupervisorInput as EntitlementNotifySupervisorInput,
|
|
9
|
+
type NotifySupervisorOutput as EntitlementNotifySupervisorOutput,
|
|
10
|
+
} from '../tools/entitlement/index.js';
|
|
11
|
+
|
|
12
|
+
export class RetrieveCustomerProfileTask extends Task<
|
|
13
|
+
FetchCustomerProfileInput,
|
|
14
|
+
FetchCustomerProfileOutput
|
|
15
|
+
> {
|
|
16
|
+
constructor() {
|
|
17
|
+
super('task-entitlement-fetch-customer', 'entitlement.fetch_customer_profile');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
idemKey(_ctx: RunContext, input: FetchCustomerProfileInput): string | undefined {
|
|
21
|
+
return input?.customerId ? `entitlement:customer:${input.customerId}` : undefined;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
policyInput(_ctx: RunContext, input: FetchCustomerProfileInput): Record<string, unknown> {
|
|
25
|
+
return {
|
|
26
|
+
action: 'fetch_customer_profile',
|
|
27
|
+
customerId: input.customerId,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
verification(): string[] {
|
|
32
|
+
return ['output.customer !== undefined'];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async execute(
|
|
36
|
+
ctx: RunContext,
|
|
37
|
+
input: FetchCustomerProfileInput
|
|
38
|
+
): Promise<FetchCustomerProfileOutput> {
|
|
39
|
+
const tool = ctx.getTool('fetch_customer_profile');
|
|
40
|
+
if (!tool) {
|
|
41
|
+
throw new Error('fetch_customer_profile tool is not registered');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const result = (await tool.call(input)) as FetchCustomerProfileOutput;
|
|
45
|
+
ctx.stream?.emit('task', {
|
|
46
|
+
taskId: this.id,
|
|
47
|
+
stage: 'customer_profile_loaded',
|
|
48
|
+
customerId: input.customerId,
|
|
49
|
+
});
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface EvaluateEntitlementTaskInput extends EvaluateEntitlementInput {
|
|
55
|
+
customer?: CustomerProfile;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export class EvaluateEntitlementTask extends Task<
|
|
59
|
+
EvaluateEntitlementTaskInput,
|
|
60
|
+
EvaluateEntitlementOutput
|
|
61
|
+
> {
|
|
62
|
+
constructor() {
|
|
63
|
+
super('task-entitlement-evaluate', 'entitlement.evaluate');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
policyInput(_ctx: RunContext, input: EvaluateEntitlementTaskInput): Record<string, unknown> {
|
|
67
|
+
return {
|
|
68
|
+
action: 'evaluate_entitlement',
|
|
69
|
+
customerId: input.customerId,
|
|
70
|
+
benefitCode: input.benefitCode,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
verification(): string[] {
|
|
75
|
+
return [
|
|
76
|
+
"output.decision === 'allow' || output.decision === 'deny'",
|
|
77
|
+
'output.policy !== undefined',
|
|
78
|
+
'Array.isArray(output.rationale)',
|
|
79
|
+
];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async execute(
|
|
83
|
+
ctx: RunContext,
|
|
84
|
+
input: EvaluateEntitlementTaskInput
|
|
85
|
+
): Promise<EvaluateEntitlementOutput> {
|
|
86
|
+
const tool = ctx.getTool('evaluate_entitlement');
|
|
87
|
+
if (!tool) {
|
|
88
|
+
throw new Error('evaluate_entitlement tool is not registered');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const result = (await tool.call(input)) as EvaluateEntitlementOutput;
|
|
92
|
+
ctx.stream?.emit('task', {
|
|
93
|
+
taskId: this.id,
|
|
94
|
+
stage: 'entitlement_evaluated',
|
|
95
|
+
customerId: input.customerId,
|
|
96
|
+
benefitCode: input.benefitCode,
|
|
97
|
+
decision: result.decision,
|
|
98
|
+
violations: result.violations,
|
|
99
|
+
});
|
|
100
|
+
return result;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export type SupervisorNotificationTaskInput = EntitlementNotifySupervisorInput & {
|
|
105
|
+
decision: 'allow' | 'deny';
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export class SupervisorNotificationTask extends Task<
|
|
109
|
+
SupervisorNotificationTaskInput,
|
|
110
|
+
EntitlementNotifySupervisorOutput
|
|
111
|
+
> {
|
|
112
|
+
constructor() {
|
|
113
|
+
super('task-entitlement-notify-supervisor', 'entitlement.notify_supervisor');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
policyInput(_ctx: RunContext, input: SupervisorNotificationTaskInput): Record<string, unknown> {
|
|
117
|
+
return {
|
|
118
|
+
action: 'notify_supervisor',
|
|
119
|
+
customerId: input.customerId,
|
|
120
|
+
channel: input.channel ?? 'email',
|
|
121
|
+
decision: input.decision,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
verification(): string[] {
|
|
126
|
+
return ['output.notified === true', 'typeof output.messageId === "string"'];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async execute(
|
|
130
|
+
ctx: RunContext,
|
|
131
|
+
input: SupervisorNotificationTaskInput
|
|
132
|
+
): Promise<EntitlementNotifySupervisorOutput> {
|
|
133
|
+
const tool = ctx.getTool('notify_supervisor');
|
|
134
|
+
if (!tool) {
|
|
135
|
+
throw new Error('notify_supervisor tool is not registered');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const normalizedInput: SupervisorNotificationTaskInput = {
|
|
139
|
+
...input,
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const evaluationOutput = ctx.outputs?.['task-entitlement-evaluate'] ?? {};
|
|
143
|
+
const customerProfile = ctx.outputs?.['task-entitlement-fetch-customer']?.customer ?? {};
|
|
144
|
+
|
|
145
|
+
const normalizeString = (value: unknown): string | undefined => {
|
|
146
|
+
if (typeof value !== 'string') {
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
const trimmed = value.trim();
|
|
150
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const customerId =
|
|
154
|
+
normalizeString(normalizedInput.customerId) ??
|
|
155
|
+
normalizeString(evaluationOutput?.customer?.id) ??
|
|
156
|
+
normalizeString(customerProfile?.id) ??
|
|
157
|
+
normalizeString(ctx.context?.facts?.customerId);
|
|
158
|
+
if (customerId) {
|
|
159
|
+
normalizedInput.customerId = customerId.toUpperCase();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const benefitCode =
|
|
163
|
+
normalizeString(evaluationOutput?.policy?.benefitCode) ??
|
|
164
|
+
normalizeString(ctx.context?.facts?.benefitCode);
|
|
165
|
+
const benefitCodeUpper = benefitCode ? benefitCode.toUpperCase() : undefined;
|
|
166
|
+
|
|
167
|
+
if (!normalizeString(normalizedInput.channel)) {
|
|
168
|
+
normalizedInput.channel = 'email';
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const decision =
|
|
172
|
+
(normalizeString(normalizedInput.decision)?.toLowerCase() as 'allow' | 'deny' | undefined) ??
|
|
173
|
+
evaluationOutput?.decision ??
|
|
174
|
+
'allow';
|
|
175
|
+
normalizedInput.decision = decision;
|
|
176
|
+
|
|
177
|
+
if (!normalizeString(normalizedInput.message)) {
|
|
178
|
+
const customerName =
|
|
179
|
+
normalizeString(customerProfile?.name) ??
|
|
180
|
+
normalizeString(evaluationOutput?.customer?.name) ??
|
|
181
|
+
'customer';
|
|
182
|
+
const renderedBenefit = benefitCodeUpper ?? 'benefit';
|
|
183
|
+
const renderedCustomer = normalizedInput.customerId ?? 'UNKNOWN-ID';
|
|
184
|
+
normalizedInput.message = `Entitlement decision ${decision} for ${customerName} (${renderedCustomer}) regarding ${renderedBenefit}.`;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const result = (await tool.call(normalizedInput)) as EntitlementNotifySupervisorOutput;
|
|
188
|
+
ctx.stream?.emit('task', {
|
|
189
|
+
taskId: this.id,
|
|
190
|
+
stage: 'supervisor_notified',
|
|
191
|
+
customerId: normalizedInput.customerId,
|
|
192
|
+
decision: normalizedInput.decision,
|
|
193
|
+
channel: result.channel,
|
|
194
|
+
});
|
|
195
|
+
return result;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import { Task, type RunContext } from '@ddse/acm-sdk';
|
|
2
|
+
import type { IncidentRecord, IncidentSeverity } from '../data/incidents.js';
|
|
3
|
+
import {
|
|
4
|
+
type FetchIncidentInput,
|
|
5
|
+
type FetchIncidentOutput,
|
|
6
|
+
type ClassifySeverityOutput,
|
|
7
|
+
type SelectQueueOutput,
|
|
8
|
+
type EscalateIncidentInput,
|
|
9
|
+
type EscalateIncidentOutput,
|
|
10
|
+
} from '../tools/incidents/index.js';
|
|
11
|
+
|
|
12
|
+
export class FetchIncidentTask extends Task<FetchIncidentInput, FetchIncidentOutput> {
|
|
13
|
+
constructor() {
|
|
14
|
+
super('task-incident-fetch', 'incident.fetch');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
idemKey(_ctx: RunContext, input: FetchIncidentInput): string | undefined {
|
|
18
|
+
return input?.incidentId ? `incident:${input.incidentId}` : undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
policyInput(_ctx: RunContext, input: FetchIncidentInput): Record<string, unknown> {
|
|
22
|
+
return {
|
|
23
|
+
action: 'fetch_incident',
|
|
24
|
+
incidentId: input.incidentId,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
verification(): string[] {
|
|
29
|
+
return ['output.incident !== undefined'];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async execute(
|
|
33
|
+
ctx: RunContext,
|
|
34
|
+
input: FetchIncidentInput
|
|
35
|
+
): Promise<FetchIncidentOutput> {
|
|
36
|
+
const tool = ctx.getTool('fetch_incident');
|
|
37
|
+
if (!tool) {
|
|
38
|
+
throw new Error('fetch_incident tool is not registered');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const result = (await tool.call(input)) as FetchIncidentOutput;
|
|
42
|
+
ctx.stream?.emit('task', {
|
|
43
|
+
taskId: this.id,
|
|
44
|
+
stage: 'incident_loaded',
|
|
45
|
+
incidentId: input.incidentId,
|
|
46
|
+
});
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface ClassifySeverityTaskInput {
|
|
52
|
+
incident?: IncidentRecord;
|
|
53
|
+
incidentId?: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export class ClassifySeverityTask extends Task<
|
|
57
|
+
ClassifySeverityTaskInput,
|
|
58
|
+
ClassifySeverityOutput
|
|
59
|
+
> {
|
|
60
|
+
constructor() {
|
|
61
|
+
super('task-incident-classify', 'incident.classify_severity');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
policyInput(_ctx: RunContext, input: ClassifySeverityTaskInput): Record<string, unknown> {
|
|
65
|
+
const incident = this.resolveIncident(_ctx, input);
|
|
66
|
+
return {
|
|
67
|
+
action: 'classify_severity',
|
|
68
|
+
incidentId: incident?.id ?? input.incidentId,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
verification(): string[] {
|
|
73
|
+
return ['output.severity !== undefined', 'typeof output.score === "number"'];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async execute(
|
|
77
|
+
ctx: RunContext,
|
|
78
|
+
input: ClassifySeverityTaskInput
|
|
79
|
+
): Promise<ClassifySeverityOutput> {
|
|
80
|
+
const tool = ctx.getTool('classify_severity');
|
|
81
|
+
if (!tool) {
|
|
82
|
+
throw new Error('classify_severity tool is not registered');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const incident = this.resolveIncident(ctx, input);
|
|
86
|
+
if (!incident) {
|
|
87
|
+
throw new Error('incident is required');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const result = (await tool.call({ incident })) as ClassifySeverityOutput;
|
|
91
|
+
ctx.stream?.emit('task', {
|
|
92
|
+
taskId: this.id,
|
|
93
|
+
stage: 'incident_severity_classified',
|
|
94
|
+
incidentId: incident.id,
|
|
95
|
+
severity: result.severity,
|
|
96
|
+
score: result.score,
|
|
97
|
+
});
|
|
98
|
+
return result;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private resolveIncident(
|
|
102
|
+
ctx: RunContext,
|
|
103
|
+
input: ClassifySeverityTaskInput
|
|
104
|
+
): IncidentRecord | undefined {
|
|
105
|
+
if (input?.incident) {
|
|
106
|
+
return input.incident;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const fetchOutput = ctx.outputs?.['task-incident-fetch'] as FetchIncidentOutput | undefined;
|
|
110
|
+
const incident = fetchOutput?.incident;
|
|
111
|
+
if (!incident) {
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (!input?.incidentId || incident.id === input.incidentId) {
|
|
116
|
+
return incident;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export interface SelectQueueTaskInput {
|
|
124
|
+
incident?: IncidentRecord;
|
|
125
|
+
incidentId?: string;
|
|
126
|
+
severityOverride?: IncidentSeverity;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export class SelectQueueTask extends Task<SelectQueueTaskInput, SelectQueueOutput> {
|
|
130
|
+
constructor() {
|
|
131
|
+
super('task-incident-route', 'incident.select_queue');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
policyInput(_ctx: RunContext, input: SelectQueueTaskInput): Record<string, unknown> {
|
|
135
|
+
const incident = this.resolveIncident(_ctx, input);
|
|
136
|
+
const severityOverride = this.resolveSeverityOverride(_ctx, input);
|
|
137
|
+
return {
|
|
138
|
+
action: 'select_queue',
|
|
139
|
+
incidentId: incident?.id ?? input.incidentId,
|
|
140
|
+
severityOverride,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
verification(): string[] {
|
|
145
|
+
return ['typeof output.queue === "string"', 'output.rule !== undefined'];
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async execute(
|
|
149
|
+
ctx: RunContext,
|
|
150
|
+
input: SelectQueueTaskInput
|
|
151
|
+
): Promise<SelectQueueOutput> {
|
|
152
|
+
const tool = ctx.getTool('select_queue');
|
|
153
|
+
if (!tool) {
|
|
154
|
+
throw new Error('select_queue tool is not registered');
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const incident = this.resolveIncident(ctx, input);
|
|
158
|
+
if (!incident) {
|
|
159
|
+
throw new Error('incident is required');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const severityOverride = this.resolveSeverityOverride(ctx, input);
|
|
163
|
+
const result = (await tool.call({ incident, severityOverride })) as SelectQueueOutput;
|
|
164
|
+
ctx.stream?.emit('task', {
|
|
165
|
+
taskId: this.id,
|
|
166
|
+
stage: 'incident_queue_selected',
|
|
167
|
+
incidentId: incident.id,
|
|
168
|
+
queue: result.queue,
|
|
169
|
+
escalationRequired: result.escalationRequired,
|
|
170
|
+
});
|
|
171
|
+
return result;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private resolveIncident(
|
|
175
|
+
ctx: RunContext,
|
|
176
|
+
input: SelectQueueTaskInput
|
|
177
|
+
): IncidentRecord | undefined {
|
|
178
|
+
if (input?.incident) {
|
|
179
|
+
return input.incident;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const fetchOutput = ctx.outputs?.['task-incident-fetch'] as FetchIncidentOutput | undefined;
|
|
183
|
+
const incident = fetchOutput?.incident;
|
|
184
|
+
if (!incident) {
|
|
185
|
+
return undefined;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (!input?.incidentId || incident.id === input.incidentId) {
|
|
189
|
+
return incident;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return undefined;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
private resolveSeverityOverride(
|
|
196
|
+
ctx: RunContext,
|
|
197
|
+
input: SelectQueueTaskInput
|
|
198
|
+
): IncidentSeverity | undefined {
|
|
199
|
+
if (input?.severityOverride) {
|
|
200
|
+
return input.severityOverride;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const classifyOutput = ctx.outputs?.['task-incident-classify'] as ClassifySeverityOutput | undefined;
|
|
204
|
+
return classifyOutput?.severity;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export class EscalateIncidentTask extends Task<
|
|
209
|
+
EscalateIncidentInput,
|
|
210
|
+
EscalateIncidentOutput
|
|
211
|
+
> {
|
|
212
|
+
constructor() {
|
|
213
|
+
super('task-incident-escalate', 'incident.escalate');
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
policyInput(_ctx: RunContext, input: EscalateIncidentInput): Record<string, unknown> {
|
|
217
|
+
const resolved = this.resolveEscalationInput(_ctx, input);
|
|
218
|
+
return {
|
|
219
|
+
action: 'escalate_incident',
|
|
220
|
+
incidentId: resolved.incidentId,
|
|
221
|
+
target: resolved.target,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
verification(): string[] {
|
|
226
|
+
return ['output.escalated === true', 'typeof output.ticketId === "string"'];
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
async execute(
|
|
230
|
+
ctx: RunContext,
|
|
231
|
+
input: EscalateIncidentInput
|
|
232
|
+
): Promise<EscalateIncidentOutput> {
|
|
233
|
+
const tool = ctx.getTool('escalate_incident');
|
|
234
|
+
if (!tool) {
|
|
235
|
+
throw new Error('escalate_incident tool is not registered');
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const resolved = this.resolveEscalationInput(ctx, input);
|
|
239
|
+
const result = (await tool.call(resolved)) as EscalateIncidentOutput;
|
|
240
|
+
ctx.stream?.emit('task', {
|
|
241
|
+
taskId: this.id,
|
|
242
|
+
stage: 'incident_escalated',
|
|
243
|
+
incidentId: resolved.incidentId,
|
|
244
|
+
target: result.target,
|
|
245
|
+
ticketId: result.ticketId,
|
|
246
|
+
});
|
|
247
|
+
return result;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
private resolveEscalationInput(
|
|
251
|
+
ctx: RunContext,
|
|
252
|
+
input: EscalateIncidentInput
|
|
253
|
+
): EscalateIncidentInput {
|
|
254
|
+
const fetchOutput = ctx.outputs?.['task-incident-fetch'] as FetchIncidentOutput | undefined;
|
|
255
|
+
const selectOutput = ctx.outputs?.['task-incident-route'] as SelectQueueOutput | undefined;
|
|
256
|
+
|
|
257
|
+
const incidentId = input?.incidentId ?? fetchOutput?.incident?.id;
|
|
258
|
+
const target = input?.target ?? selectOutput?.rule?.escalatesTo ?? 'incident-manager@northwind.example';
|
|
259
|
+
const reason =
|
|
260
|
+
input?.reason ??
|
|
261
|
+
(selectOutput?.rationale?.join(' ') || 'Escalation triggered by routing decision.');
|
|
262
|
+
|
|
263
|
+
if (!incidentId) {
|
|
264
|
+
throw new Error('incidentId is required for escalation');
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (!target) {
|
|
268
|
+
throw new Error('Escalation target could not be determined');
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return {
|
|
272
|
+
incidentId,
|
|
273
|
+
target,
|
|
274
|
+
reason,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
}
|