@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.
Files changed (182) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +113 -0
  3. package/bin/acm-demo.ts +495 -0
  4. package/data/coaching/agents.json +16 -0
  5. package/data/coaching/transcripts.json +37 -0
  6. package/data/documents.json +72 -0
  7. package/data/entitlement/customers.json +38 -0
  8. package/data/entitlement/policies.json +38 -0
  9. package/data/incidents/incidents.json +30 -0
  10. package/data/incidents/routing_rules.json +36 -0
  11. package/data/invoices/invoices.json +38 -0
  12. package/data/invoices/purchase-orders.json +38 -0
  13. package/data/issues.json +99 -0
  14. package/data/knowledge/docs/kb-001.md +7 -0
  15. package/data/knowledge/docs/kb-002.md +7 -0
  16. package/data/knowledge/docs/kb-003.md +9 -0
  17. package/data/knowledge/index.json +25 -0
  18. package/data/orders.json +106 -0
  19. package/dist/bin/acm-demo.d.ts +3 -0
  20. package/dist/bin/acm-demo.d.ts.map +1 -0
  21. package/dist/bin/acm-demo.js +392 -0
  22. package/dist/bin/acm-demo.js.map +1 -0
  23. package/dist/src/context/directives.d.ts +3 -0
  24. package/dist/src/context/directives.d.ts.map +1 -0
  25. package/dist/src/context/directives.js +325 -0
  26. package/dist/src/context/directives.js.map +1 -0
  27. package/dist/src/context/index.d.ts +2 -0
  28. package/dist/src/context/index.d.ts.map +1 -0
  29. package/dist/src/context/index.js +2 -0
  30. package/dist/src/context/index.js.map +1 -0
  31. package/dist/src/data/coaching.d.ts +19 -0
  32. package/dist/src/data/coaching.d.ts.map +1 -0
  33. package/dist/src/data/coaching.js +22 -0
  34. package/dist/src/data/coaching.js.map +1 -0
  35. package/dist/src/data/entitlement.d.ts +25 -0
  36. package/dist/src/data/entitlement.d.ts.map +1 -0
  37. package/dist/src/data/entitlement.js +26 -0
  38. package/dist/src/data/entitlement.js.map +1 -0
  39. package/dist/src/data/incidents.d.ts +23 -0
  40. package/dist/src/data/incidents.d.ts.map +1 -0
  41. package/dist/src/data/incidents.js +37 -0
  42. package/dist/src/data/incidents.js.map +1 -0
  43. package/dist/src/data/invoices.d.ts +34 -0
  44. package/dist/src/data/invoices.d.ts.map +1 -0
  45. package/dist/src/data/invoices.js +49 -0
  46. package/dist/src/data/invoices.js.map +1 -0
  47. package/dist/src/data/knowledge.d.ts +11 -0
  48. package/dist/src/data/knowledge.d.ts.map +1 -0
  49. package/dist/src/data/knowledge.js +57 -0
  50. package/dist/src/data/knowledge.js.map +1 -0
  51. package/dist/src/data/loader.d.ts +4 -0
  52. package/dist/src/data/loader.d.ts.map +1 -0
  53. package/dist/src/data/loader.js +69 -0
  54. package/dist/src/data/loader.js.map +1 -0
  55. package/dist/src/examples/scenarios.d.ts +23 -0
  56. package/dist/src/examples/scenarios.d.ts.map +1 -0
  57. package/dist/src/examples/scenarios.js +609 -0
  58. package/dist/src/examples/scenarios.js.map +1 -0
  59. package/dist/src/goals/index.d.ts +8 -0
  60. package/dist/src/goals/index.d.ts.map +1 -0
  61. package/dist/src/goals/index.js +12 -0
  62. package/dist/src/goals/index.js.map +1 -0
  63. package/dist/src/policy.d.ts +5 -0
  64. package/dist/src/policy.d.ts.map +1 -0
  65. package/dist/src/policy.js +24 -0
  66. package/dist/src/policy.js.map +1 -0
  67. package/dist/src/registries.d.ts +18 -0
  68. package/dist/src/registries.d.ts.map +1 -0
  69. package/dist/src/registries.js +38 -0
  70. package/dist/src/registries.js.map +1 -0
  71. package/dist/src/renderer.d.ts +9 -0
  72. package/dist/src/renderer.d.ts.map +1 -0
  73. package/dist/src/renderer.js +76 -0
  74. package/dist/src/renderer.js.map +1 -0
  75. package/dist/src/search/bm25.d.ts +68 -0
  76. package/dist/src/search/bm25.d.ts.map +1 -0
  77. package/dist/src/search/bm25.js +131 -0
  78. package/dist/src/search/bm25.js.map +1 -0
  79. package/dist/src/search/index.d.ts +2 -0
  80. package/dist/src/search/index.d.ts.map +1 -0
  81. package/dist/src/search/index.js +3 -0
  82. package/dist/src/search/index.js.map +1 -0
  83. package/dist/src/tasks/coaching.d.ts +30 -0
  84. package/dist/src/tasks/coaching.d.ts.map +1 -0
  85. package/dist/src/tasks/coaching.js +143 -0
  86. package/dist/src/tasks/coaching.js.map +1 -0
  87. package/dist/src/tasks/entitlement.d.ts +29 -0
  88. package/dist/src/tasks/entitlement.d.ts.map +1 -0
  89. package/dist/src/tasks/entitlement.js +135 -0
  90. package/dist/src/tasks/entitlement.js.map +1 -0
  91. package/dist/src/tasks/incidents.d.ts +42 -0
  92. package/dist/src/tasks/incidents.d.ts.map +1 -0
  93. package/dist/src/tasks/incidents.js +189 -0
  94. package/dist/src/tasks/incidents.js.map +1 -0
  95. package/dist/src/tasks/index.d.ts +7 -0
  96. package/dist/src/tasks/index.d.ts.map +1 -0
  97. package/dist/src/tasks/index.js +7 -0
  98. package/dist/src/tasks/index.js.map +1 -0
  99. package/dist/src/tasks/invoices.d.ts +40 -0
  100. package/dist/src/tasks/invoices.d.ts.map +1 -0
  101. package/dist/src/tasks/invoices.js +180 -0
  102. package/dist/src/tasks/invoices.js.map +1 -0
  103. package/dist/src/tasks/knowledge.d.ts +23 -0
  104. package/dist/src/tasks/knowledge.d.ts.map +1 -0
  105. package/dist/src/tasks/knowledge.js +115 -0
  106. package/dist/src/tasks/knowledge.js.map +1 -0
  107. package/dist/src/tasks/legacy.d.ts +50 -0
  108. package/dist/src/tasks/legacy.d.ts.map +1 -0
  109. package/dist/src/tasks/legacy.js +85 -0
  110. package/dist/src/tasks/legacy.js.map +1 -0
  111. package/dist/src/tools/coaching/index.d.ts +49 -0
  112. package/dist/src/tools/coaching/index.d.ts.map +1 -0
  113. package/dist/src/tools/coaching/index.js +119 -0
  114. package/dist/src/tools/coaching/index.js.map +1 -0
  115. package/dist/src/tools/entitlement/index.d.ts +52 -0
  116. package/dist/src/tools/entitlement/index.d.ts.map +1 -0
  117. package/dist/src/tools/entitlement/index.js +120 -0
  118. package/dist/src/tools/entitlement/index.js.map +1 -0
  119. package/dist/src/tools/incidents/index.d.ts +55 -0
  120. package/dist/src/tools/incidents/index.d.ts.map +1 -0
  121. package/dist/src/tools/incidents/index.js +109 -0
  122. package/dist/src/tools/incidents/index.js.map +1 -0
  123. package/dist/src/tools/index.d.ts +90 -0
  124. package/dist/src/tools/index.d.ts.map +1 -0
  125. package/dist/src/tools/index.js +109 -0
  126. package/dist/src/tools/index.js.map +1 -0
  127. package/dist/src/tools/invoices/index.d.ts +56 -0
  128. package/dist/src/tools/invoices/index.d.ts.map +1 -0
  129. package/dist/src/tools/invoices/index.js +85 -0
  130. package/dist/src/tools/invoices/index.js.map +1 -0
  131. package/dist/src/tools/knowledge/index.d.ts +52 -0
  132. package/dist/src/tools/knowledge/index.d.ts.map +1 -0
  133. package/dist/src/tools/knowledge/index.js +120 -0
  134. package/dist/src/tools/knowledge/index.js.map +1 -0
  135. package/dist/tests/bm25.test.d.ts +2 -0
  136. package/dist/tests/bm25.test.d.ts.map +1 -0
  137. package/dist/tests/bm25.test.js +98 -0
  138. package/dist/tests/bm25.test.js.map +1 -0
  139. package/dist/tests/integration.test.d.ts +2 -0
  140. package/dist/tests/integration.test.d.ts.map +1 -0
  141. package/dist/tests/integration.test.js +126 -0
  142. package/dist/tests/integration.test.js.map +1 -0
  143. package/dist/tests/plan-hydration.test.d.ts +2 -0
  144. package/dist/tests/plan-hydration.test.d.ts.map +1 -0
  145. package/dist/tests/plan-hydration.test.js +28 -0
  146. package/dist/tests/plan-hydration.test.js.map +1 -0
  147. package/dist/tsconfig.tsbuildinfo +1 -0
  148. package/docs/examples-architecture.md +144 -0
  149. package/docs/successrun.md +1022 -0
  150. package/package.json +33 -0
  151. package/src/context/directives.ts +366 -0
  152. package/src/context/index.ts +1 -0
  153. package/src/data/coaching.ts +50 -0
  154. package/src/data/entitlement.ts +60 -0
  155. package/src/data/incidents.ts +78 -0
  156. package/src/data/invoices.ts +103 -0
  157. package/src/data/knowledge.ts +77 -0
  158. package/src/data/loader.ts +80 -0
  159. package/src/examples/scenarios.ts +724 -0
  160. package/src/goals/index.ts +18 -0
  161. package/src/policy.ts +30 -0
  162. package/src/registries.ts +48 -0
  163. package/src/renderer.ts +82 -0
  164. package/src/search/bm25.ts +173 -0
  165. package/src/search/index.ts +2 -0
  166. package/src/tasks/coaching.ts +217 -0
  167. package/src/tasks/entitlement.ts +197 -0
  168. package/src/tasks/incidents.ts +277 -0
  169. package/src/tasks/index.ts +6 -0
  170. package/src/tasks/invoices.ts +269 -0
  171. package/src/tasks/knowledge.ts +169 -0
  172. package/src/tasks/legacy.ts +112 -0
  173. package/src/tools/coaching/index.ts +197 -0
  174. package/src/tools/entitlement/index.ts +199 -0
  175. package/src/tools/incidents/index.ts +185 -0
  176. package/src/tools/index.ts +192 -0
  177. package/src/tools/invoices/index.ts +165 -0
  178. package/src/tools/knowledge/index.ts +203 -0
  179. package/tests/bm25.test.ts +129 -0
  180. package/tests/integration.test.ts +163 -0
  181. package/tests/plan-hydration.test.ts +33 -0
  182. 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
+ }
@@ -0,0 +1,6 @@
1
+ export * from './legacy.js';
2
+ export * from './entitlement.js';
3
+ export * from './knowledge.js';
4
+ export * from './incidents.js';
5
+ export * from './invoices.js';
6
+ export * from './coaching.js';