@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,18 @@
1
+ // Goal/context index derived from scenario catalog
2
+ import type { Goal, Context } from '@ddse/acm-sdk';
3
+ import { scenarios, type ScenarioKey } from '../examples/scenarios.js';
4
+
5
+ type GoalMap = Record<ScenarioKey, Goal> & Record<string, Goal>;
6
+ type ContextMap = Record<ScenarioKey, Context> & Record<string, Context>;
7
+
8
+ export const goals = Object.keys(scenarios).reduce((acc, key) => {
9
+ const scenario = scenarios[key as ScenarioKey];
10
+ acc[key] = scenario.goal;
11
+ return acc;
12
+ }, {} as GoalMap);
13
+
14
+ export const contexts = Object.keys(scenarios).reduce((acc, key) => {
15
+ const scenario = scenarios[key as ScenarioKey];
16
+ acc[key] = scenario.context;
17
+ return acc;
18
+ }, {} as ContextMap);
package/src/policy.ts ADDED
@@ -0,0 +1,30 @@
1
+ // Simple policy engine implementation
2
+ import { PolicyEngine, type PolicyDecision } from '@ddse/acm-sdk';
3
+
4
+ export class SimplePolicyEngine implements PolicyEngine {
5
+ async evaluate(
6
+ action: 'plan.admit' | 'task.pre' | 'task.post',
7
+ payload: any
8
+ ): Promise<PolicyDecision> {
9
+ // Simple rule: deny high-risk refunds
10
+ if (action === 'task.pre' && payload.action === 'create_refund') {
11
+ // In a real implementation, we'd check risk from context
12
+ // For demo, randomly allow/deny based on amount
13
+ if (payload.amount && payload.amount > 100) {
14
+ return {
15
+ allow: false,
16
+ reason: 'Refund amount exceeds policy limit',
17
+ };
18
+ }
19
+ }
20
+
21
+ // Default: allow
22
+ return {
23
+ allow: true,
24
+ limits: {
25
+ timeoutMs: 30000,
26
+ retries: 3,
27
+ },
28
+ };
29
+ }
30
+ }
@@ -0,0 +1,48 @@
1
+ // Concrete registry implementations
2
+ import { CapabilityRegistry, ToolRegistry, type Capability, type Task, type Tool } from '@ddse/acm-sdk';
3
+
4
+ export class SimpleCapabilityRegistry extends CapabilityRegistry {
5
+ private tasks = new Map<string, Task>();
6
+ private capabilities = new Map<string, Capability>();
7
+
8
+ register(capability: Capability, task: Task): void {
9
+ this.capabilities.set(capability.name, capability);
10
+ this.tasks.set(capability.name, task);
11
+ }
12
+
13
+ list(): Capability[] {
14
+ return Array.from(this.capabilities.values());
15
+ }
16
+
17
+ has(name: string): boolean {
18
+ return this.capabilities.has(name);
19
+ }
20
+
21
+ resolve(name: string): Task | undefined {
22
+ return this.tasks.get(name);
23
+ }
24
+
25
+ inputSchema(name: string): unknown | undefined {
26
+ return this.capabilities.get(name)?.inputSchema;
27
+ }
28
+
29
+ outputSchema(name: string): unknown | undefined {
30
+ return this.capabilities.get(name)?.outputSchema;
31
+ }
32
+ }
33
+
34
+ export class SimpleToolRegistry extends ToolRegistry {
35
+ private tools = new Map<string, Tool>();
36
+
37
+ register(tool: Tool): void {
38
+ this.tools.set(tool.name(), tool);
39
+ }
40
+
41
+ get(name: string): Tool | undefined {
42
+ return this.tools.get(name);
43
+ }
44
+
45
+ list(): string[] {
46
+ return Array.from(this.tools.keys());
47
+ }
48
+ }
@@ -0,0 +1,82 @@
1
+ // CLI renderer for streaming output
2
+ export class CLIRenderer {
3
+ private taskStatus = new Map<string, string>();
4
+
5
+ renderPlannerToken(chunk: any): void {
6
+ if (chunk.delta) {
7
+ process.stdout.write(chunk.delta);
8
+ }
9
+
10
+ const summary: string[] = [];
11
+ if (typeof chunk.plans === 'number') {
12
+ const label = chunk.plans === 1 ? 'plan' : 'plans';
13
+ summary.push(`Generated ${chunk.plans} ${label}.`);
14
+ }
15
+ if (chunk.rationale) {
16
+ summary.push(chunk.rationale);
17
+ }
18
+
19
+ if (summary.length > 0) {
20
+ process.stdout.write(`\n\n${summary.join('\n\n')}\n\n`);
21
+ }
22
+
23
+ if (chunk.done && summary.length === 0) {
24
+ process.stdout.write('\n\n');
25
+ }
26
+ }
27
+
28
+ renderTaskUpdate(update: any): void {
29
+ const { taskId, status, step, output, error } = update;
30
+
31
+ if (status) {
32
+ this.taskStatus.set(taskId, status);
33
+ }
34
+
35
+ if (status === 'running') {
36
+ console.log(`\n[${taskId}] Task started`);
37
+ } else if (status === 'completed') {
38
+ console.log(`[${taskId}] ✓ Task completed`);
39
+ if (output) {
40
+ console.log(` Output: ${JSON.stringify(output, null, 2)}`);
41
+ }
42
+ } else if (status === 'failed') {
43
+ console.error(`[${taskId}] ✗ Task failed: ${error}`);
44
+ } else if (step) {
45
+ console.log(`[${taskId}] → ${step}`);
46
+ }
47
+ }
48
+
49
+ renderLedgerEntry(entry: any): void {
50
+ const icon = this.getIconForType(entry.type);
51
+ console.log(`${icon} [${entry.type}] ${JSON.stringify(entry.details)}`);
52
+ }
53
+
54
+ renderSummary(result: any): void {
55
+ console.log('\n' + '='.repeat(60));
56
+ console.log('EXECUTION SUMMARY');
57
+ console.log('='.repeat(60));
58
+ const taskEntries = Object.entries(result.outputsByTask ?? {});
59
+ const ledgerEntries = Array.isArray(result.ledger) ? result.ledger.length : 0;
60
+ console.log(`Total tasks: ${taskEntries.length}`);
61
+ console.log(`Ledger entries: ${ledgerEntries}`);
62
+ console.log('\nOutputs:');
63
+ for (const [taskId, record] of taskEntries) {
64
+ const output = record && typeof record === 'object' && 'output' in record ? record.output : record;
65
+ console.log(` ${taskId}:`, JSON.stringify(output, null, 2));
66
+ }
67
+ }
68
+
69
+ private getIconForType(type: string): string {
70
+ switch (type) {
71
+ case 'PLAN_SELECTED': return '📋';
72
+ case 'GUARD_EVAL': return '🚦';
73
+ case 'TASK_START': return '▶️ ';
74
+ case 'TASK_END': return '✅';
75
+ case 'POLICY_PRE': return '🔒';
76
+ case 'POLICY_POST': return '🔓';
77
+ case 'VERIFICATION': return '✔️ ';
78
+ case 'ERROR': return '❌';
79
+ default: return '•';
80
+ }
81
+ }
82
+ }
@@ -0,0 +1,173 @@
1
+ // Simple BM25 search implementation for ACM examples
2
+
3
+ /**
4
+ * Document interface for BM25 search
5
+ */
6
+ export interface Document {
7
+ id: string;
8
+ title?: string;
9
+ content: string;
10
+ [key: string]: any;
11
+ }
12
+
13
+ /**
14
+ * Search result with score
15
+ */
16
+ export interface SearchResult {
17
+ document: Document;
18
+ score: number;
19
+ }
20
+
21
+ /**
22
+ * BM25 parameters
23
+ */
24
+ export interface BM25Params {
25
+ k1?: number; // Term frequency saturation (default: 1.5)
26
+ b?: number; // Length normalization (default: 0.75)
27
+ }
28
+
29
+ /**
30
+ * Simple tokenizer
31
+ */
32
+ function tokenize(text?: string | null): string[] {
33
+ if (!text) return [];
34
+
35
+ return text
36
+ .toLowerCase()
37
+ .replace(/[^\w\s]/g, ' ')
38
+ .split(/\s+/)
39
+ .filter((token) => token.length > 0);
40
+ }
41
+
42
+ /**
43
+ * BM25 Search Engine
44
+ *
45
+ * Implements the BM25 ranking function for full-text search.
46
+ * BM25 is a probabilistic ranking function used by search engines
47
+ * to estimate the relevance of documents to a query.
48
+ */
49
+ export class BM25Search {
50
+ private documents: Document[] = [];
51
+ private documentTokens: Map<string, string[]> = new Map();
52
+ private documentFrequency: Map<string, number> = new Map();
53
+ private averageDocumentLength: number = 0;
54
+ private k1: number;
55
+ private b: number;
56
+
57
+ constructor(params: BM25Params = {}) {
58
+ this.k1 = params.k1 ?? 1.5;
59
+ this.b = params.b ?? 0.75;
60
+ }
61
+
62
+ /**
63
+ * Index documents for search
64
+ */
65
+ index(documents: Document[]): void {
66
+ this.documents = documents;
67
+ this.documentTokens.clear();
68
+ this.documentFrequency.clear();
69
+
70
+ // Tokenize documents
71
+ let totalLength = 0;
72
+ for (const doc of documents) {
73
+ const text = this.extractText(doc);
74
+ const tokens = tokenize(text);
75
+ this.documentTokens.set(doc.id, tokens);
76
+ totalLength += tokens.length;
77
+
78
+ // Update document frequency
79
+ const uniqueTokens = new Set(tokens);
80
+ for (const token of uniqueTokens) {
81
+ this.documentFrequency.set(token, (this.documentFrequency.get(token) || 0) + 1);
82
+ }
83
+ }
84
+
85
+ this.averageDocumentLength = totalLength / documents.length;
86
+ }
87
+
88
+ /**
89
+ * Extract searchable text from document
90
+ */
91
+ private extractText(doc: Document): string {
92
+ const parts: string[] = [];
93
+ if (doc.title) parts.push(doc.title);
94
+ if (doc.content) parts.push(doc.content);
95
+ return parts.join(' ');
96
+ }
97
+
98
+ /**
99
+ * Calculate IDF (Inverse Document Frequency)
100
+ */
101
+ private idf(term: string): number {
102
+ const df = this.documentFrequency.get(term) || 0;
103
+ if (df === 0) return 0;
104
+
105
+ const n = this.documents.length;
106
+ return Math.log((n - df + 0.5) / (df + 0.5) + 1);
107
+ }
108
+
109
+ /**
110
+ * Calculate BM25 score for a document
111
+ */
112
+ private score(docId: string, queryTokens: string[]): number {
113
+ const tokens = this.documentTokens.get(docId);
114
+ if (!tokens) return 0;
115
+
116
+ const docLength = tokens.length;
117
+ let score = 0;
118
+
119
+ for (const queryToken of queryTokens) {
120
+ const termFreq = tokens.filter((t) => t === queryToken).length;
121
+ if (termFreq === 0) continue;
122
+
123
+ const idf = this.idf(queryToken);
124
+ const numerator = termFreq * (this.k1 + 1);
125
+ const denominator = termFreq + this.k1 * (1 - this.b + this.b * (docLength / this.averageDocumentLength));
126
+
127
+ score += idf * (numerator / denominator);
128
+ }
129
+
130
+ return score;
131
+ }
132
+
133
+ /**
134
+ * Search for documents matching the query
135
+ */
136
+ search(query: string, limit: number = 10): SearchResult[] {
137
+ if (this.documents.length === 0) {
138
+ return [];
139
+ }
140
+
141
+ const queryTokens = tokenize(query);
142
+ if (queryTokens.length === 0) {
143
+ return [];
144
+ }
145
+
146
+ // Score all documents
147
+ const results: SearchResult[] = [];
148
+ for (const doc of this.documents) {
149
+ const score = this.score(doc.id, queryTokens);
150
+ if (score > 0) {
151
+ results.push({ document: doc, score });
152
+ }
153
+ }
154
+
155
+ // Sort by score (descending) and limit
156
+ results.sort((a, b) => b.score - a.score);
157
+ return results.slice(0, limit);
158
+ }
159
+
160
+ /**
161
+ * Get all indexed documents
162
+ */
163
+ getDocuments(): Document[] {
164
+ return this.documents;
165
+ }
166
+
167
+ /**
168
+ * Get document count
169
+ */
170
+ getDocumentCount(): number {
171
+ return this.documents.length;
172
+ }
173
+ }
@@ -0,0 +1,2 @@
1
+ // Search functionality for ACM examples
2
+ export { BM25Search, type Document, type SearchResult, type BM25Params } from './bm25.js';
@@ -0,0 +1,217 @@
1
+ import { Task, type RunContext } from '@ddse/acm-sdk';
2
+ import {
3
+ type AnalyzeTranscriptInput,
4
+ type AnalyzeTranscriptOutput,
5
+ type GenerateFeedbackInput,
6
+ type GenerateFeedbackOutput,
7
+ type LogCoachingNoteInput,
8
+ type LogCoachingNoteOutput,
9
+ } from '../tools/coaching/index.js';
10
+
11
+ export class AnalyzeTranscriptTask extends Task<
12
+ AnalyzeTranscriptInput,
13
+ AnalyzeTranscriptOutput
14
+ > {
15
+ constructor() {
16
+ super('task-coaching-analyze-transcript', 'coaching.analyze_transcript');
17
+ }
18
+
19
+ idemKey(_ctx: RunContext, input: AnalyzeTranscriptInput): string | undefined {
20
+ return input?.transcriptId ? `coaching:transcript:${input.transcriptId}` : undefined;
21
+ }
22
+
23
+ policyInput(_ctx: RunContext, input: AnalyzeTranscriptInput): Record<string, unknown> {
24
+ return {
25
+ action: 'analyze_transcript',
26
+ transcriptId: input.transcriptId,
27
+ };
28
+ }
29
+
30
+ verification(): string[] {
31
+ return ['typeof output.sentimentScore === "number"', 'Array.isArray(output.highlights)'];
32
+ }
33
+
34
+ async execute(
35
+ ctx: RunContext,
36
+ input: AnalyzeTranscriptInput
37
+ ): Promise<AnalyzeTranscriptOutput> {
38
+ const tool = ctx.getTool('analyze_transcript');
39
+ if (!tool) {
40
+ throw new Error('analyze_transcript tool is not registered');
41
+ }
42
+
43
+ const result = (await tool.call(input)) as AnalyzeTranscriptOutput;
44
+ ctx.stream?.emit('task', {
45
+ taskId: this.id,
46
+ stage: 'coaching_transcript_analyzed',
47
+ transcriptId: input.transcriptId,
48
+ sentimentScore: result.sentimentScore,
49
+ complianceScore: result.complianceScore,
50
+ });
51
+ return result;
52
+ }
53
+ }
54
+
55
+ export interface GenerateFeedbackTaskInput {
56
+ transcriptId?: string;
57
+ metrics?: GenerateFeedbackInput['metrics'];
58
+ }
59
+
60
+ export class GenerateFeedbackTask extends Task<
61
+ GenerateFeedbackTaskInput,
62
+ GenerateFeedbackOutput
63
+ > {
64
+ constructor() {
65
+ super('task-coaching-generate-feedback', 'coaching.generate_feedback');
66
+ }
67
+
68
+ policyInput(ctx: RunContext, input: GenerateFeedbackTaskInput): Record<string, unknown> {
69
+ const resolved = this.resolveInput(ctx, input);
70
+ return {
71
+ action: 'generate_feedback',
72
+ transcriptId: resolved.transcriptId,
73
+ metrics: resolved.metrics,
74
+ };
75
+ }
76
+
77
+ verification(): string[] {
78
+ return ['typeof output.feedbackSummary === "string"', 'Array.isArray(output.actionItems)'];
79
+ }
80
+
81
+ async execute(
82
+ ctx: RunContext,
83
+ input: GenerateFeedbackTaskInput
84
+ ): Promise<GenerateFeedbackOutput> {
85
+ const tool = ctx.getTool('generate_feedback');
86
+ if (!tool) {
87
+ throw new Error('generate_feedback tool is not registered');
88
+ }
89
+
90
+ const resolved = this.resolveInput(ctx, input);
91
+ const result = (await tool.call(resolved)) as GenerateFeedbackOutput;
92
+ ctx.stream?.emit('task', {
93
+ taskId: this.id,
94
+ stage: 'coaching_feedback_generated',
95
+ transcriptId: resolved.transcriptId,
96
+ escalationRequired: result.escalationRequired,
97
+ });
98
+ return result;
99
+ }
100
+
101
+ private resolveInput(
102
+ ctx: RunContext,
103
+ input: GenerateFeedbackTaskInput
104
+ ): GenerateFeedbackInput {
105
+ const analyzeOutput = ctx.outputs?.['task-coaching-analyze-transcript'] as
106
+ | AnalyzeTranscriptOutput
107
+ | undefined;
108
+
109
+ const transcriptId =
110
+ input?.transcriptId ?? analyzeOutput?.transcript?.id;
111
+ if (!transcriptId) {
112
+ throw new Error('transcriptId is required for feedback generation');
113
+ }
114
+
115
+ const metrics =
116
+ input?.metrics ??
117
+ (analyzeOutput
118
+ ? {
119
+ sentimentScore: analyzeOutput.sentimentScore,
120
+ complianceScore: analyzeOutput.complianceScore,
121
+ complianceBreaches: analyzeOutput.complianceBreaches,
122
+ highlights: analyzeOutput.highlights,
123
+ summary: analyzeOutput.summary,
124
+ }
125
+ : undefined);
126
+
127
+ if (!metrics) {
128
+ throw new Error('metrics are required for feedback generation');
129
+ }
130
+
131
+ return {
132
+ transcriptId,
133
+ metrics,
134
+ };
135
+ }
136
+ }
137
+
138
+ export interface LogCoachingNoteTaskInput extends Partial<LogCoachingNoteInput> {}
139
+
140
+ export class LogCoachingNoteTask extends Task<
141
+ LogCoachingNoteTaskInput,
142
+ LogCoachingNoteOutput
143
+ > {
144
+ constructor() {
145
+ super('task-coaching-log-note', 'coaching.log_note');
146
+ }
147
+
148
+ policyInput(ctx: RunContext, input: LogCoachingNoteTaskInput): Record<string, unknown> {
149
+ const resolved = this.resolveInput(ctx, input);
150
+ return {
151
+ action: 'log_coaching_note',
152
+ agentId: resolved.agentId,
153
+ escalationRequired: resolved.escalationRequired,
154
+ };
155
+ }
156
+
157
+ verification(): string[] {
158
+ return ['output.stored === true', 'typeof output.logId === "string"'];
159
+ }
160
+
161
+ async execute(
162
+ ctx: RunContext,
163
+ input: LogCoachingNoteTaskInput
164
+ ): Promise<LogCoachingNoteOutput> {
165
+ const tool = ctx.getTool('log_coaching_note');
166
+ if (!tool) {
167
+ throw new Error('log_coaching_note tool is not registered');
168
+ }
169
+
170
+ const resolved = this.resolveInput(ctx, input);
171
+ const result = (await tool.call(resolved)) as LogCoachingNoteOutput;
172
+ ctx.stream?.emit('task', {
173
+ taskId: this.id,
174
+ stage: 'coaching_note_logged',
175
+ agentId: resolved.agentId,
176
+ escalationNotified: result.escalationNotified,
177
+ });
178
+ return result;
179
+ }
180
+
181
+ private resolveInput(
182
+ ctx: RunContext,
183
+ input: LogCoachingNoteTaskInput
184
+ ): LogCoachingNoteInput {
185
+ const analyzeOutput = ctx.outputs?.['task-coaching-analyze-transcript'] as
186
+ | AnalyzeTranscriptOutput
187
+ | undefined;
188
+ const feedbackOutput = ctx.outputs?.['task-coaching-generate-feedback'] as
189
+ | GenerateFeedbackOutput
190
+ | undefined;
191
+
192
+ const agentId = input.agentId ?? analyzeOutput?.transcript?.agentId;
193
+ if (!agentId) {
194
+ throw new Error('agentId is required to log coaching note');
195
+ }
196
+
197
+ const feedbackSummary = input.feedbackSummary ?? feedbackOutput?.feedbackSummary;
198
+ if (!feedbackSummary) {
199
+ throw new Error('feedbackSummary is required to log coaching note');
200
+ }
201
+
202
+ const actionItems = input.actionItems ?? feedbackOutput?.actionItems;
203
+ if (!actionItems) {
204
+ throw new Error('actionItems are required to log coaching note');
205
+ }
206
+
207
+ const escalationRequired =
208
+ input.escalationRequired ?? feedbackOutput?.escalationRequired ?? false;
209
+
210
+ return {
211
+ agentId,
212
+ feedbackSummary,
213
+ actionItems,
214
+ escalationRequired,
215
+ };
216
+ }
217
+ }