opc-agent 2.1.0 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. package/README.md +603 -545
  2. package/dist/channels/voice.d.ts +59 -0
  3. package/dist/channels/voice.js +351 -1
  4. package/dist/cli.js +172 -1
  5. package/dist/core/agent.d.ts +4 -0
  6. package/dist/core/agent.js +35 -0
  7. package/dist/core/collaboration.d.ts +89 -0
  8. package/dist/core/collaboration.js +201 -0
  9. package/dist/deploy/index.d.ts +40 -0
  10. package/dist/deploy/index.js +261 -0
  11. package/dist/index.d.ts +7 -1
  12. package/dist/index.js +47 -3
  13. package/dist/mcp/servers/calculator-mcp.d.ts +3 -0
  14. package/dist/mcp/servers/calculator-mcp.js +65 -0
  15. package/dist/mcp/servers/crypto-mcp.d.ts +3 -0
  16. package/dist/mcp/servers/crypto-mcp.js +108 -0
  17. package/dist/mcp/servers/database-mcp.d.ts +3 -0
  18. package/dist/mcp/servers/database-mcp.js +73 -0
  19. package/dist/mcp/servers/datetime-mcp.d.ts +3 -0
  20. package/dist/mcp/servers/datetime-mcp.js +71 -0
  21. package/dist/mcp/servers/filesystem.d.ts +3 -0
  22. package/dist/mcp/servers/filesystem.js +101 -0
  23. package/dist/mcp/servers/github-mcp.d.ts +3 -0
  24. package/dist/mcp/servers/github-mcp.js +60 -0
  25. package/dist/mcp/servers/index.d.ts +21 -0
  26. package/dist/mcp/servers/index.js +50 -0
  27. package/dist/mcp/servers/json-mcp.d.ts +3 -0
  28. package/dist/mcp/servers/json-mcp.js +126 -0
  29. package/dist/mcp/servers/memory-mcp.d.ts +3 -0
  30. package/dist/mcp/servers/memory-mcp.js +60 -0
  31. package/dist/mcp/servers/regex-mcp.d.ts +3 -0
  32. package/dist/mcp/servers/regex-mcp.js +56 -0
  33. package/dist/mcp/servers/web-mcp.d.ts +3 -0
  34. package/dist/mcp/servers/web-mcp.js +51 -0
  35. package/dist/schema/oad.d.ts +292 -12
  36. package/dist/schema/oad.js +12 -1
  37. package/dist/security/guardrails.d.ts +50 -0
  38. package/dist/security/guardrails.js +197 -0
  39. package/dist/studio/server.d.ts +31 -1
  40. package/dist/studio/server.js +154 -3
  41. package/dist/studio-ui/index.html +1278 -662
  42. package/dist/tools/integrations/calendar.d.ts +3 -0
  43. package/dist/tools/integrations/calendar.js +73 -0
  44. package/dist/tools/integrations/code-exec.d.ts +3 -0
  45. package/dist/tools/integrations/code-exec.js +42 -0
  46. package/dist/tools/integrations/csv-analyzer.d.ts +3 -0
  47. package/dist/tools/integrations/csv-analyzer.js +142 -0
  48. package/dist/tools/integrations/database.d.ts +3 -0
  49. package/dist/tools/integrations/database.js +44 -0
  50. package/dist/tools/integrations/email-send.d.ts +3 -0
  51. package/dist/tools/integrations/email-send.js +104 -0
  52. package/dist/tools/integrations/git-tool.d.ts +3 -0
  53. package/dist/tools/integrations/git-tool.js +49 -0
  54. package/dist/tools/integrations/github-tool.d.ts +3 -0
  55. package/dist/tools/integrations/github-tool.js +77 -0
  56. package/dist/tools/integrations/image-gen.d.ts +3 -0
  57. package/dist/tools/integrations/image-gen.js +58 -0
  58. package/dist/tools/integrations/index.d.ts +30 -0
  59. package/dist/tools/integrations/index.js +107 -0
  60. package/dist/tools/integrations/jira.d.ts +3 -0
  61. package/dist/tools/integrations/jira.js +85 -0
  62. package/dist/tools/integrations/notion.d.ts +3 -0
  63. package/dist/tools/integrations/notion.js +71 -0
  64. package/dist/tools/integrations/npm-tool.d.ts +3 -0
  65. package/dist/tools/integrations/npm-tool.js +49 -0
  66. package/dist/tools/integrations/pdf-reader.d.ts +3 -0
  67. package/dist/tools/integrations/pdf-reader.js +91 -0
  68. package/dist/tools/integrations/slack.d.ts +3 -0
  69. package/dist/tools/integrations/slack.js +67 -0
  70. package/dist/tools/integrations/summarizer.d.ts +3 -0
  71. package/dist/tools/integrations/summarizer.js +49 -0
  72. package/dist/tools/integrations/translator.d.ts +3 -0
  73. package/dist/tools/integrations/translator.js +48 -0
  74. package/dist/tools/integrations/trello.d.ts +3 -0
  75. package/dist/tools/integrations/trello.js +60 -0
  76. package/dist/tools/integrations/vector-search.d.ts +3 -0
  77. package/dist/tools/integrations/vector-search.js +44 -0
  78. package/dist/tools/integrations/web-scraper.d.ts +3 -0
  79. package/dist/tools/integrations/web-scraper.js +48 -0
  80. package/dist/tools/integrations/web-search.d.ts +3 -0
  81. package/dist/tools/integrations/web-search.js +60 -0
  82. package/dist/tools/integrations/webhook.d.ts +3 -0
  83. package/dist/tools/integrations/webhook.js +39 -0
  84. package/dist/ui/components.d.ts +10 -0
  85. package/dist/ui/components.js +123 -0
  86. package/package.json +3 -3
  87. package/src/channels/voice.ts +365 -0
  88. package/src/cli.ts +176 -2
  89. package/src/core/agent.ts +38 -0
  90. package/src/core/collaboration.ts +275 -0
  91. package/src/deploy/index.ts +255 -0
  92. package/src/index.ts +21 -1
  93. package/src/mcp/servers/calculator-mcp.ts +65 -0
  94. package/src/mcp/servers/crypto-mcp.ts +73 -0
  95. package/src/mcp/servers/database-mcp.ts +72 -0
  96. package/src/mcp/servers/datetime-mcp.ts +69 -0
  97. package/src/mcp/servers/filesystem.ts +66 -0
  98. package/src/mcp/servers/github-mcp.ts +58 -0
  99. package/src/mcp/servers/index.ts +63 -0
  100. package/src/mcp/servers/json-mcp.ts +102 -0
  101. package/src/mcp/servers/memory-mcp.ts +56 -0
  102. package/src/mcp/servers/regex-mcp.ts +53 -0
  103. package/src/mcp/servers/web-mcp.ts +49 -0
  104. package/src/schema/oad.ts +13 -0
  105. package/src/security/guardrails.ts +248 -0
  106. package/src/studio/server.ts +166 -4
  107. package/src/studio-ui/index.html +1278 -662
  108. package/src/tools/integrations/calendar.ts +73 -0
  109. package/src/tools/integrations/code-exec.ts +39 -0
  110. package/src/tools/integrations/csv-analyzer.ts +92 -0
  111. package/src/tools/integrations/database.ts +44 -0
  112. package/src/tools/integrations/email-send.ts +76 -0
  113. package/src/tools/integrations/git-tool.ts +42 -0
  114. package/src/tools/integrations/github-tool.ts +76 -0
  115. package/src/tools/integrations/image-gen.ts +56 -0
  116. package/src/tools/integrations/index.ts +92 -0
  117. package/src/tools/integrations/jira.ts +83 -0
  118. package/src/tools/integrations/notion.ts +71 -0
  119. package/src/tools/integrations/npm-tool.ts +48 -0
  120. package/src/tools/integrations/pdf-reader.ts +58 -0
  121. package/src/tools/integrations/slack.ts +65 -0
  122. package/src/tools/integrations/summarizer.ts +49 -0
  123. package/src/tools/integrations/translator.ts +48 -0
  124. package/src/tools/integrations/trello.ts +60 -0
  125. package/src/tools/integrations/vector-search.ts +42 -0
  126. package/src/tools/integrations/web-scraper.ts +47 -0
  127. package/src/tools/integrations/web-search.ts +58 -0
  128. package/src/tools/integrations/webhook.ts +38 -0
  129. package/src/ui/components.ts +127 -0
  130. package/tests/brain-seed-extended.test.ts +490 -0
  131. package/tests/collaboration.test.ts +319 -0
  132. package/tests/deploy-and-dag.test.ts +196 -0
  133. package/tests/guardrails.test.ts +177 -0
  134. package/tests/integrations.test.ts +249 -0
  135. package/tests/mcp-servers.test.ts +260 -0
  136. package/tests/voice-enhanced.test.ts +169 -0
  137. package/dist/dtv/data.d.ts +0 -18
  138. package/dist/dtv/data.js +0 -25
  139. package/dist/dtv/trust.d.ts +0 -19
  140. package/dist/dtv/trust.js +0 -40
  141. package/dist/dtv/value.d.ts +0 -23
  142. package/dist/dtv/value.js +0 -38
  143. package/dist/marketplace/index.d.ts +0 -34
  144. package/dist/marketplace/index.js +0 -202
@@ -0,0 +1,197 @@
1
+ "use strict";
2
+ /**
3
+ * Guardrails Module - v2.1.0
4
+ * Input/output guardrails for LLM safety: PII, toxicity, injection, compliance.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.GuardrailManager = void 0;
8
+ exports.createGuardrailsFromConfig = createGuardrailsFromConfig;
9
+ // ── Built-in Patterns ───────────────────────────────────────
10
+ const PII_PATTERNS = {
11
+ email: /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g,
12
+ phone: /(\+?1[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}/g,
13
+ ssn: /\b\d{3}-\d{2}-\d{4}\b/g,
14
+ creditCard: /\b(?:\d{4}[-\s]?){3}\d{4}\b/g,
15
+ ipAddress: /\b(?:\d{1,3}\.){3}\d{1,3}\b/g,
16
+ };
17
+ const INJECTION_PATTERNS = [
18
+ /ignore\s+(all\s+)?previous\s+instructions/i,
19
+ /ignore\s+(all\s+)?above\s+instructions/i,
20
+ /system\s*prompt\s*:/i,
21
+ /you\s+are\s+now\s+(?:a|an|the)\s+/i,
22
+ /act\s+as\s+(?:a|an)\s+/i,
23
+ /pretend\s+(?:you(?:'re|\s+are)\s+)?/i,
24
+ /new\s+instruction[s]?\s*:/i,
25
+ /override\s+(?:your\s+)?(?:instructions|rules|guidelines)/i,
26
+ /jailbreak/i,
27
+ /DAN\s+mode/i,
28
+ ];
29
+ const TOXICITY_KEYWORDS = [
30
+ 'kill yourself', 'kys', 'go die', 'hate you',
31
+ 'stupid idiot', 'worthless', 'piece of shit',
32
+ ];
33
+ const COMPLIANCE_PATTERNS = [
34
+ { pattern: /(?:you\s+should\s+)?(?:buy|sell|invest\s+in)\s+(?:stocks?|crypto|bitcoin)/i, label: 'financial advice' },
35
+ { pattern: /(?:you\s+(?:have|probably\s+have)|diagnos(?:e|is))\s+(?:\w+\s+){0,3}(?:disease|syndrome|disorder|cancer)/i, label: 'medical diagnosis' },
36
+ { pattern: /(?:legal(?:ly)?|sue|lawsuit|court)\s+(?:you\s+should|advice)/i, label: 'legal advice' },
37
+ ];
38
+ // ── Rule Executors ──────────────────────────────────────────
39
+ function checkPII(text, action) {
40
+ const violations = [];
41
+ let redacted = text;
42
+ for (const [type, pattern] of Object.entries(PII_PATTERNS)) {
43
+ const cloned = new RegExp(pattern.source, pattern.flags);
44
+ const matches = text.match(cloned);
45
+ if (matches) {
46
+ violations.push({ rule: 'pii-detector', action, detail: `Found ${type}: ${matches.length} match(es)` });
47
+ redacted = redacted.replace(cloned, '[REDACTED]');
48
+ }
49
+ }
50
+ return { violations, redactedText: redacted };
51
+ }
52
+ function checkInjection(text) {
53
+ const violations = [];
54
+ for (const pattern of INJECTION_PATTERNS) {
55
+ if (pattern.test(text)) {
56
+ violations.push({ rule: 'prompt-injection', action: 'block', detail: `Matched pattern: ${pattern.source}` });
57
+ break; // one is enough
58
+ }
59
+ }
60
+ return violations;
61
+ }
62
+ function checkToxicity(text, extraKeywords) {
63
+ const lower = text.toLowerCase();
64
+ const keywords = [...TOXICITY_KEYWORDS, ...(extraKeywords ?? [])];
65
+ for (const kw of keywords) {
66
+ if (lower.includes(kw.toLowerCase())) {
67
+ return [{ rule: 'toxicity', action: 'block', detail: `Matched keyword: "${kw}"` }];
68
+ }
69
+ }
70
+ return [];
71
+ }
72
+ function checkTopicRestriction(text, config) {
73
+ const denyTopics = config?.denyTopics ?? [];
74
+ const lower = text.toLowerCase();
75
+ for (const topic of denyTopics) {
76
+ if (lower.includes(topic.toLowerCase())) {
77
+ return [{ rule: 'topic-restrictor', action: 'block', detail: `Blocked topic: "${topic}"` }];
78
+ }
79
+ }
80
+ return [];
81
+ }
82
+ function checkLength(text, config) {
83
+ const maxChars = config?.maxChars ?? 10000;
84
+ if (text.length > maxChars) {
85
+ return [{ rule: 'length-limit', action: 'warn', detail: `Response length ${text.length} exceeds max ${maxChars}` }];
86
+ }
87
+ return [];
88
+ }
89
+ function checkLanguage(text, config) {
90
+ const allowed = config?.allowedLanguages ?? [];
91
+ if (allowed.length === 0)
92
+ return [];
93
+ // Simple heuristic: check if text contains CJK characters
94
+ const hasCJK = /[\u4e00-\u9fff\u3040-\u309f\u30a0-\u30ff]/.test(text);
95
+ const hasLatin = /[a-zA-Z]{3,}/.test(text);
96
+ if (allowed.includes('en') && !allowed.includes('zh') && hasCJK && !hasLatin) {
97
+ return [{ rule: 'language-filter', action: 'block', detail: 'Non-allowed language detected' }];
98
+ }
99
+ if (allowed.includes('zh') && !allowed.includes('en') && hasLatin && !hasCJK) {
100
+ return [{ rule: 'language-filter', action: 'block', detail: 'Non-allowed language detected' }];
101
+ }
102
+ return [];
103
+ }
104
+ function checkCompliance(text) {
105
+ for (const { pattern, label } of COMPLIANCE_PATTERNS) {
106
+ if (pattern.test(text)) {
107
+ return [{ rule: 'compliance-filter', action: 'block', detail: `Potential ${label} detected` }];
108
+ }
109
+ }
110
+ return [];
111
+ }
112
+ // ── Guardrail Manager ───────────────────────────────────────
113
+ class GuardrailManager {
114
+ config;
115
+ constructor(config) {
116
+ this.config = config;
117
+ }
118
+ async checkInput(message) {
119
+ return this.runRules(message, this.config.input ?? []);
120
+ }
121
+ async checkOutput(response) {
122
+ return this.runRules(response, this.config.output ?? []);
123
+ }
124
+ async runRules(text, rules) {
125
+ const allViolations = [];
126
+ let blocked = false;
127
+ let warned = false;
128
+ let redacted = false;
129
+ let redactedText = text;
130
+ let blockMessage = '';
131
+ for (const rule of rules) {
132
+ let violations = [];
133
+ switch (rule.name) {
134
+ case 'pii-detector': {
135
+ const result = checkPII(text, rule.action);
136
+ violations = result.violations;
137
+ if (violations.length > 0 && rule.action === 'redact') {
138
+ redacted = true;
139
+ redactedText = result.redactedText;
140
+ }
141
+ break;
142
+ }
143
+ case 'prompt-injection':
144
+ violations = checkInjection(text);
145
+ break;
146
+ case 'toxicity':
147
+ violations = checkToxicity(text, rule.config?.keywords);
148
+ break;
149
+ case 'topic-restrictor':
150
+ violations = checkTopicRestriction(text, rule.config);
151
+ break;
152
+ case 'length-limit':
153
+ violations = checkLength(text, rule.config);
154
+ break;
155
+ case 'language-filter':
156
+ violations = checkLanguage(text, rule.config);
157
+ break;
158
+ case 'compliance-filter':
159
+ violations = checkCompliance(text);
160
+ break;
161
+ default:
162
+ // Unknown rule — skip
163
+ break;
164
+ }
165
+ if (violations.length > 0) {
166
+ // Override action from rule config
167
+ violations = violations.map(v => ({ ...v, action: rule.action }));
168
+ allViolations.push(...violations);
169
+ if (rule.action === 'block') {
170
+ blocked = true;
171
+ blockMessage = `Message blocked by ${rule.name}: ${violations[0].detail}`;
172
+ }
173
+ else if (rule.action === 'warn') {
174
+ warned = true;
175
+ }
176
+ }
177
+ }
178
+ return {
179
+ passed: allViolations.length === 0,
180
+ blocked,
181
+ warned,
182
+ redacted,
183
+ message: blocked ? blockMessage : undefined,
184
+ redactedText: redacted ? redactedText : undefined,
185
+ violations: allViolations,
186
+ };
187
+ }
188
+ }
189
+ exports.GuardrailManager = GuardrailManager;
190
+ // ── Factory from OAD config ─────────────────────────────────
191
+ function createGuardrailsFromConfig(config) {
192
+ return new GuardrailManager({
193
+ input: config.input,
194
+ output: config.output,
195
+ });
196
+ }
197
+ //# sourceMappingURL=guardrails.js.map
@@ -1,5 +1,28 @@
1
1
  import { IncomingMessage, ServerResponse } from 'http';
2
2
  import { Tracer } from '../telemetry';
3
+ export interface WorkflowNode {
4
+ id: string;
5
+ type: 'agent' | 'tool' | 'condition' | 'loop' | 'parallel' | 'input' | 'output';
6
+ name: string;
7
+ x: number;
8
+ y: number;
9
+ config: Record<string, any>;
10
+ }
11
+ export interface WorkflowEdge {
12
+ id: string;
13
+ from: string;
14
+ to: string;
15
+ fromPort: string;
16
+ toPort: string;
17
+ }
18
+ export interface WorkflowDefinition {
19
+ id: string;
20
+ name: string;
21
+ nodes: WorkflowNode[];
22
+ edges: WorkflowEdge[];
23
+ created: string;
24
+ updated: string;
25
+ }
3
26
  interface StudioConfig {
4
27
  port: number;
5
28
  agentDir: string;
@@ -26,7 +49,13 @@ declare class StudioServer {
26
49
  private getMemoryStats;
27
50
  private getSkills;
28
51
  private getTools;
29
- private getWorkflows;
52
+ private getWorkflowsDir;
53
+ private listWorkflows;
54
+ private getWorkflowById;
55
+ private saveWorkflow;
56
+ private deleteWorkflow;
57
+ private runWorkflow;
58
+ private topoSort;
30
59
  private getJobs;
31
60
  private getRecentLogs;
32
61
  private getAnalytics;
@@ -57,6 +86,7 @@ declare class StudioServer {
57
86
  private loadOAD;
58
87
  private loadPackageJson;
59
88
  serveStatic(req: IncomingMessage, res: ServerResponse, url: URL): void;
89
+ private handlePlaygroundChat;
60
90
  private readBody;
61
91
  }
62
92
  export { StudioServer, StudioConfig };
@@ -107,6 +107,28 @@ class StudioServer {
107
107
  const route = url.pathname.replace('/api/', '');
108
108
  try {
109
109
  let data;
110
+ // Dynamic workflow routes (parameterized)
111
+ if (route.match(/^workflows\/[^/]+\/run$/) && req.method === 'POST') {
112
+ const wfId = route.split('/')[1];
113
+ data = await this.runWorkflow(wfId);
114
+ res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
115
+ res.end(JSON.stringify(data));
116
+ return;
117
+ }
118
+ if (route.match(/^workflows\/[^/]+$/) && req.method === 'GET') {
119
+ const wfId = route.split('/')[1];
120
+ data = this.getWorkflowById(wfId);
121
+ res.writeHead(data.error ? 404 : 200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
122
+ res.end(JSON.stringify(data));
123
+ return;
124
+ }
125
+ if (route.match(/^workflows\/[^/]+$/) && req.method === 'DELETE') {
126
+ const wfId = route.split('/')[1];
127
+ data = this.deleteWorkflow(wfId);
128
+ res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
129
+ res.end(JSON.stringify(data));
130
+ return;
131
+ }
110
132
  switch (route) {
111
133
  case 'modules':
112
134
  data = await this.getModulesStatus();
@@ -139,7 +161,18 @@ class StudioServer {
139
161
  data = await this.getTools();
140
162
  break;
141
163
  case 'workflows/list':
142
- data = await this.getWorkflows();
164
+ data = this.listWorkflows();
165
+ break;
166
+ case 'workflows':
167
+ if (req.method === 'POST')
168
+ data = await this.saveWorkflow(req);
169
+ else if (req.method === 'GET')
170
+ data = this.listWorkflows();
171
+ else {
172
+ res.writeHead(405);
173
+ res.end();
174
+ return;
175
+ }
143
176
  break;
144
177
  case 'jobs/list':
145
178
  data = await this.getJobs();
@@ -207,6 +240,16 @@ class StudioServer {
207
240
  case 'telemetry/metrics':
208
241
  data = this.tracer ? this.tracer.getMetrics() : [];
209
242
  break;
243
+ case 'playground/chat':
244
+ if (req.method === 'POST') {
245
+ return this.handlePlaygroundChat(req, res);
246
+ }
247
+ res.writeHead(405);
248
+ res.end();
249
+ return;
250
+ case 'playground/models':
251
+ data = { models: ['gpt-4o', 'gpt-4o-mini', 'claude-sonnet-4', 'claude-haiku', 'gemini-2.0-flash', 'deepseek-v3'] };
252
+ break;
210
253
  default:
211
254
  res.writeHead(404, { 'Content-Type': 'application/json' });
212
255
  res.end(JSON.stringify({ error: 'Not found' }));
@@ -347,9 +390,96 @@ class StudioServer {
347
390
  return { tools: [] };
348
391
  }
349
392
  }
350
- async getWorkflows() {
393
+ getWorkflowsDir() {
394
+ const dir = (0, path_1.join)(this.config.agentDir, '.opc', 'workflows');
395
+ if (!(0, fs_1.existsSync)(dir))
396
+ (0, fs_1.mkdirSync)(dir, { recursive: true });
397
+ return dir;
398
+ }
399
+ listWorkflows() {
400
+ const dir = this.getWorkflowsDir();
401
+ const files = require('fs').readdirSync(dir).filter((f) => f.endsWith('.json'));
402
+ const workflows = files.map((f) => {
403
+ try {
404
+ return JSON.parse((0, fs_1.readFileSync)((0, path_1.join)(dir, f), 'utf-8'));
405
+ }
406
+ catch {
407
+ return null;
408
+ }
409
+ }).filter(Boolean);
410
+ // Also include OAD-defined workflows
351
411
  const oad = this.loadOAD();
352
- return { workflows: oad?.spec?.workflows || [] };
412
+ const oadWorkflows = (oad?.spec?.workflows || []).map((w, i) => ({
413
+ id: `oad-${i}`,
414
+ name: w.name || `Workflow ${i + 1}`,
415
+ nodes: [],
416
+ edges: [],
417
+ steps: w.steps,
418
+ source: 'oad',
419
+ }));
420
+ return { workflows: [...workflows, ...oadWorkflows] };
421
+ }
422
+ getWorkflowById(id) {
423
+ const filePath = (0, path_1.join)(this.getWorkflowsDir(), `${id}.json`);
424
+ if (!(0, fs_1.existsSync)(filePath))
425
+ return { error: 'Workflow not found' };
426
+ return JSON.parse((0, fs_1.readFileSync)(filePath, 'utf-8'));
427
+ }
428
+ async saveWorkflow(req) {
429
+ const body = await this.readBody(req);
430
+ const workflow = JSON.parse(body);
431
+ if (!workflow.id)
432
+ workflow.id = `wf-${Date.now()}`;
433
+ workflow.updated = new Date().toISOString();
434
+ if (!workflow.created)
435
+ workflow.created = workflow.updated;
436
+ const filePath = (0, path_1.join)(this.getWorkflowsDir(), `${workflow.id}.json`);
437
+ (0, fs_1.writeFileSync)(filePath, JSON.stringify(workflow, null, 2));
438
+ return { success: true, id: workflow.id };
439
+ }
440
+ deleteWorkflow(id) {
441
+ const filePath = (0, path_1.join)(this.getWorkflowsDir(), `${id}.json`);
442
+ if ((0, fs_1.existsSync)(filePath))
443
+ require('fs').unlinkSync(filePath);
444
+ return { success: true };
445
+ }
446
+ async runWorkflow(id) {
447
+ const wf = this.getWorkflowById(id);
448
+ if ('error' in wf)
449
+ return wf;
450
+ // Basic topological execution simulation
451
+ const results = {};
452
+ const sorted = this.topoSort(wf.nodes, wf.edges);
453
+ for (const node of sorted) {
454
+ results[node.id] = { type: node.type, name: node.name, status: 'completed', output: `[simulated output for ${node.name}]` };
455
+ }
456
+ return { workflowId: id, status: 'completed', results };
457
+ }
458
+ topoSort(nodes, edges) {
459
+ const nodeMap = new Map(nodes.map(n => [n.id, n]));
460
+ const inDegree = new Map();
461
+ const adj = new Map();
462
+ for (const n of nodes) {
463
+ inDegree.set(n.id, 0);
464
+ adj.set(n.id, []);
465
+ }
466
+ for (const e of edges) {
467
+ adj.get(e.from)?.push(e.to);
468
+ inDegree.set(e.to, (inDegree.get(e.to) || 0) + 1);
469
+ }
470
+ const queue = nodes.filter(n => (inDegree.get(n.id) || 0) === 0);
471
+ const result = [];
472
+ while (queue.length > 0) {
473
+ const node = queue.shift();
474
+ result.push(node);
475
+ for (const next of (adj.get(node.id) || [])) {
476
+ const d = (inDegree.get(next) || 1) - 1;
477
+ inDegree.set(next, d);
478
+ if (d === 0)
479
+ queue.push(nodeMap.get(next));
480
+ }
481
+ }
482
+ return result;
353
483
  }
354
484
  async getJobs() {
355
485
  const oad = this.loadOAD();
@@ -612,6 +742,27 @@ class StudioServer {
612
742
  res.writeHead(200, { 'Content-Type': contentType });
613
743
  res.end(content);
614
744
  }
745
+ async handlePlaygroundChat(req, res) {
746
+ const body = JSON.parse(await this.readBody(req));
747
+ const { messages = [], model = 'gpt-4o', temperature = 0.7, systemPrompt } = body;
748
+ res.writeHead(200, {
749
+ 'Content-Type': 'text/event-stream',
750
+ 'Cache-Control': 'no-cache',
751
+ 'Connection': 'keep-alive',
752
+ 'Access-Control-Allow-Origin': '*',
753
+ });
754
+ // Simulated streaming response for playground demo
755
+ const allMsgs = systemPrompt ? [{ role: 'system', content: systemPrompt }, ...messages] : messages;
756
+ const lastMsg = allMsgs[allMsgs.length - 1]?.content || '';
757
+ const response = `This is a playground demo response to: "${lastMsg}"\n\nModel: ${model}, Temperature: ${temperature}\nMessages in context: ${allMsgs.length}`;
758
+ const words = response.split(' ');
759
+ for (let i = 0; i < words.length; i++) {
760
+ const chunk = (i === 0 ? '' : ' ') + words[i];
761
+ res.write(`data: ${JSON.stringify({ content: chunk })}\n\n`);
762
+ }
763
+ res.write('data: [DONE]\n\n');
764
+ res.end();
765
+ }
615
766
  readBody(req) {
616
767
  return new Promise((resolve, reject) => {
617
768
  let body = '';