thumbgate 1.15.0 → 1.16.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 (129) hide show
  1. package/.claude-plugin/marketplace.json +6 -6
  2. package/.claude-plugin/plugin.json +3 -3
  3. package/.well-known/llms.txt +5 -5
  4. package/.well-known/mcp/server-card.json +1 -1
  5. package/README.md +59 -35
  6. package/adapters/chatgpt/openapi.yaml +118 -2
  7. package/adapters/claude/.mcp.json +2 -2
  8. package/adapters/mcp/server-stdio.js +210 -84
  9. package/adapters/opencode/opencode.json +1 -1
  10. package/bench/prompt-eval-suite.json +5 -1
  11. package/bin/cli.js +157 -8
  12. package/config/evals/agent-safety-eval.json +338 -22
  13. package/config/gates/routine.json +43 -0
  14. package/config/github-about.json +3 -3
  15. package/config/model-candidates.json +131 -0
  16. package/openapi/openapi.yaml +118 -2
  17. package/package.json +55 -48
  18. package/public/blog.html +7 -7
  19. package/public/codex-plugin.html +6 -6
  20. package/public/compare.html +29 -23
  21. package/public/dashboard.html +82 -10
  22. package/public/guide.html +28 -28
  23. package/public/index.html +216 -98
  24. package/public/learn.html +50 -22
  25. package/public/lessons.html +1 -1
  26. package/public/numbers.html +17 -17
  27. package/public/pro.html +82 -18
  28. package/scripts/agent-audit-trace.js +55 -0
  29. package/scripts/agent-memory-lifecycle.js +96 -0
  30. package/scripts/agent-readiness-plan.js +118 -0
  31. package/scripts/agentic-data-pipeline.js +21 -1
  32. package/scripts/agents-sdk-sandbox-plan.js +57 -0
  33. package/scripts/ai-org-governance.js +98 -0
  34. package/scripts/ai-search-distribution.js +43 -0
  35. package/scripts/artifact-agent-plan.js +81 -0
  36. package/scripts/billing.js +27 -8
  37. package/scripts/cli-schema.js +18 -2
  38. package/scripts/code-mode-mcp-plan.js +71 -0
  39. package/scripts/context-engine.js +1 -2
  40. package/scripts/context-manager.js +4 -1
  41. package/scripts/dashboard-render-spec.js +1 -1
  42. package/scripts/dashboard.js +275 -9
  43. package/scripts/decision-journal.js +13 -3
  44. package/scripts/document-workflow-governance.js +62 -0
  45. package/scripts/enterprise-agent-rollout.js +34 -0
  46. package/scripts/experience-replay-governance.js +69 -0
  47. package/scripts/export-hf-dataset.js +1 -1
  48. package/scripts/feedback-loop.js +92 -4
  49. package/scripts/feedback-to-rules.js +17 -23
  50. package/scripts/gates-engine.js +4 -6
  51. package/scripts/growth-campaigns.js +49 -0
  52. package/scripts/harness-selector.js +16 -4
  53. package/scripts/hybrid-supervisor-agent.js +64 -0
  54. package/scripts/inference-cache-policy.js +72 -0
  55. package/scripts/inference-economics.js +53 -0
  56. package/scripts/internal-agent-bootstrap.js +12 -2
  57. package/scripts/knowledge-layer-plan.js +108 -0
  58. package/scripts/lesson-inference.js +183 -44
  59. package/scripts/lesson-search.js +4 -1
  60. package/scripts/llm-client.js +157 -26
  61. package/scripts/mailer/resend-mailer.js +112 -1
  62. package/scripts/mcp-transport-strategy.js +66 -0
  63. package/scripts/memory-store-governance.js +60 -0
  64. package/scripts/meta-agent-loop.js +7 -13
  65. package/scripts/model-access-eligibility.js +38 -0
  66. package/scripts/model-migration-readiness.js +55 -0
  67. package/scripts/operational-integrity.js +96 -3
  68. package/scripts/otel-declarative-config.js +56 -0
  69. package/scripts/perplexity-client.js +1 -1
  70. package/scripts/post-training-governance.js +34 -0
  71. package/scripts/private-core-boundary.js +72 -0
  72. package/scripts/production-agent-readiness.js +40 -0
  73. package/scripts/prompt-eval.js +564 -32
  74. package/scripts/prompt-programs.js +93 -0
  75. package/scripts/provider-action-normalizer.js +585 -0
  76. package/scripts/scaling-law-claims.js +60 -0
  77. package/scripts/security-scanner.js +1 -1
  78. package/scripts/self-distill-agent.js +7 -32
  79. package/scripts/seo-gsd.js +232 -55
  80. package/scripts/skill-rag-router.js +53 -0
  81. package/scripts/spec-gate.js +1 -1
  82. package/scripts/student-consistent-training.js +73 -0
  83. package/scripts/synthetic-data-provenance.js +98 -0
  84. package/scripts/task-context-result.js +81 -0
  85. package/scripts/telemetry-analytics.js +149 -0
  86. package/scripts/thompson-sampling.js +2 -2
  87. package/scripts/token-savings.js +7 -6
  88. package/scripts/token-tco.js +46 -0
  89. package/scripts/tool-registry.js +63 -3
  90. package/scripts/verification-loop.js +10 -1
  91. package/scripts/verifier-scoring.js +71 -0
  92. package/scripts/workflow-sentinel.js +284 -28
  93. package/scripts/workspace-agent-routines.js +118 -0
  94. package/src/api/server.js +381 -120
  95. package/scripts/analytics-report.js +0 -328
  96. package/scripts/autonomous-workflow.js +0 -377
  97. package/scripts/billing-setup.js +0 -109
  98. package/scripts/creator-campaigns.js +0 -239
  99. package/scripts/cross-encoder-reranker.js +0 -235
  100. package/scripts/daemon-manager.js +0 -108
  101. package/scripts/decision-trace.js +0 -354
  102. package/scripts/delegation-runtime.js +0 -896
  103. package/scripts/dispatch-brief.js +0 -159
  104. package/scripts/distribution-surfaces.js +0 -110
  105. package/scripts/feedback-history-distiller.js +0 -382
  106. package/scripts/funnel-analytics.js +0 -35
  107. package/scripts/history-distiller.js +0 -200
  108. package/scripts/hosted-job-launcher.js +0 -256
  109. package/scripts/intent-router.js +0 -392
  110. package/scripts/lesson-reranker.js +0 -263
  111. package/scripts/lesson-retrieval.js +0 -148
  112. package/scripts/managed-lesson-agent.js +0 -183
  113. package/scripts/operational-dashboard.js +0 -103
  114. package/scripts/operational-summary.js +0 -129
  115. package/scripts/operator-artifacts.js +0 -608
  116. package/scripts/optimize-context.js +0 -17
  117. package/scripts/org-dashboard.js +0 -206
  118. package/scripts/partner-orchestration.js +0 -146
  119. package/scripts/predictive-insights.js +0 -356
  120. package/scripts/pulse.js +0 -80
  121. package/scripts/reflector-agent.js +0 -221
  122. package/scripts/sales-pipeline.js +0 -681
  123. package/scripts/session-episode-store.js +0 -329
  124. package/scripts/session-health-sensor.js +0 -242
  125. package/scripts/session-report.js +0 -120
  126. package/scripts/swarm-coordinator.js +0 -81
  127. package/scripts/tool-kpi-tracker.js +0 -12
  128. package/scripts/webhook-delivery.js +0 -62
  129. package/scripts/workflow-sprint-intake.js +0 -475
@@ -0,0 +1,93 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ function normalizeText(value) {
5
+ if (value === undefined || value === null) return '';
6
+ return String(value).trim();
7
+ }
8
+
9
+ function normalizeArray(value) {
10
+ if (!Array.isArray(value)) return [];
11
+ return value.map(normalizeText).filter(Boolean);
12
+ }
13
+
14
+ function buildCrePromptProgram(input = {}) {
15
+ const name = normalizeText(input.name) || 'thumbgate_prompt_program';
16
+ const context = normalizeText(input.context);
17
+ const role = normalizeText(input.role);
18
+ const expectations = normalizeArray(input.expectations);
19
+ const outputFormat = normalizeText(input.outputFormat) || 'valid Markdown';
20
+ const lengthCap = normalizeText(input.lengthCap) || '<= 250 words';
21
+ const tone = normalizeText(input.tone) || 'plain, practical, non-hype';
22
+ const safetyBoundaries = normalizeArray(input.safetyBoundaries);
23
+ const examples = Array.isArray(input.examples) ? input.examples : [];
24
+ const missing = [];
25
+ if (!context) missing.push('context');
26
+ if (!role) missing.push('role');
27
+ if (expectations.length === 0) missing.push('expectations');
28
+
29
+ return {
30
+ name,
31
+ pattern: 'CRE',
32
+ status: missing.length === 0 ? 'ready' : 'needs_context',
33
+ missing,
34
+ program: {
35
+ context,
36
+ role,
37
+ expectations,
38
+ outputFormat,
39
+ lengthCap,
40
+ tone,
41
+ safetyBoundaries: safetyBoundaries.length > 0
42
+ ? safetyBoundaries
43
+ : ['Never invent data; say unknown when evidence is missing.'],
44
+ examples: examples.map((example, index) => ({
45
+ id: `example_${index + 1}`,
46
+ input: normalizeText(example?.input),
47
+ output: normalizeText(example?.output),
48
+ })).filter((example) => example.input && example.output),
49
+ },
50
+ prompt: [
51
+ `Context: ${context || '[required]'}`,
52
+ `Role: ${role || '[required]'}`,
53
+ `Expectations: ${expectations.length > 0 ? expectations.join('; ') : '[required]'}`,
54
+ `Output format: ${outputFormat}.`,
55
+ `Length cap: ${lengthCap}.`,
56
+ `Tone: ${tone}.`,
57
+ `Safety: ${(safetyBoundaries.length > 0 ? safetyBoundaries : ['Never invent data; say unknown when evidence is missing.']).join('; ')}`,
58
+ ].join('\n'),
59
+ };
60
+ }
61
+
62
+ function reviewPromptProgram(input = {}) {
63
+ const program = input.program?.pattern === 'CRE'
64
+ ? input.program
65
+ : buildCrePromptProgram(input);
66
+ const issues = [];
67
+ for (const field of program.missing || []) {
68
+ issues.push({ field, issue: 'missing_cre_component' });
69
+ }
70
+ if (!/json|markdown|table|schema|yaml/i.test(program.program.outputFormat || '')) {
71
+ issues.push({ field: 'outputFormat', issue: 'not_paste_ready' });
72
+ }
73
+ if (!/[<≤=]|\bmax\b|\bwords?\b|\bbullets?\b|\bsections?\b/i.test(program.program.lengthCap || '')) {
74
+ issues.push({ field: 'lengthCap', issue: 'missing_length_constraint' });
75
+ }
76
+ if ((program.program.examples || []).length === 0) {
77
+ issues.push({ field: 'examples', issue: 'zero_shot_only' });
78
+ }
79
+ const blocking = issues.filter((issue) => issue.issue !== 'zero_shot_only');
80
+ return {
81
+ status: blocking.length === 0 ? 'pass' : 'fail',
82
+ issueCount: issues.length,
83
+ issues,
84
+ recommendation: blocking.length === 0
85
+ ? 'Prompt program is constrained enough for routine use; add examples only if outputs drift.'
86
+ : 'Add missing CRE, paste-ready output shape, and length constraints before using this in a critical workflow.',
87
+ };
88
+ }
89
+
90
+ module.exports = {
91
+ buildCrePromptProgram,
92
+ reviewPromptProgram,
93
+ };
@@ -0,0 +1,585 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const DEFAULT_SOFT_TOKEN_LIMIT = 8000;
5
+ const DEFAULT_MAX_PARALLEL_BRANCHES = 4;
6
+
7
+ const EDIT_TOOL_NAMES = new Set(['Edit', 'Write', 'MultiEdit', 'file.write']);
8
+ const MCP_PRIMITIVE_BY_METHOD = {
9
+ 'tools/call': 'tool',
10
+ 'resources/read': 'resource',
11
+ 'prompts/get': 'prompt',
12
+ };
13
+
14
+ function asObject(value) {
15
+ return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
16
+ }
17
+
18
+ function asArray(value) {
19
+ return Array.isArray(value) ? value : [];
20
+ }
21
+
22
+ function appendDashOnce(output) {
23
+ if (output.endsWith('-')) return output;
24
+ return `${output}-`;
25
+ }
26
+
27
+ function isAsciiAlphanumeric(ch) {
28
+ const code = ch.charCodeAt(0);
29
+ return (code >= 48 && code <= 57) || (code >= 97 && code <= 122);
30
+ }
31
+
32
+ function isWhitespace(ch) {
33
+ const code = ch.charCodeAt(0);
34
+ return code === 9 || code === 10 || code === 11 || code === 12 || code === 13 || code === 32;
35
+ }
36
+
37
+ function trimDashes(value) {
38
+ let start = 0;
39
+ let end = value.length;
40
+ while (start < end && value[start] === '-') start += 1;
41
+ while (end > start && value[end - 1] === '-') end -= 1;
42
+ return value.slice(start, end);
43
+ }
44
+
45
+ function normalizeToken(value, allowedPunctuation = '._-') {
46
+ const text = String(value || '').trim().toLowerCase();
47
+ let output = '';
48
+ for (const ch of text) {
49
+ if (isAsciiAlphanumeric(ch) || allowedPunctuation.includes(ch)) {
50
+ output += ch;
51
+ continue;
52
+ }
53
+ output = appendDashOnce(output);
54
+ }
55
+ return trimDashes(output);
56
+ }
57
+
58
+ function normalizeCommandText(value) {
59
+ const text = String(value || '').trim().toLowerCase();
60
+ let output = '';
61
+ for (const ch of text) {
62
+ if (isWhitespace(ch)) {
63
+ output = output.endsWith(' ') ? output : `${output} `;
64
+ continue;
65
+ }
66
+ output += ch;
67
+ }
68
+ return output.trim();
69
+ }
70
+
71
+ function containsAny(value, needles) {
72
+ return needles.some((needle) => value.includes(needle));
73
+ }
74
+
75
+ function normalizeProvider(provider) {
76
+ const value = String(provider || '').trim().toLowerCase();
77
+ if (!value) return 'unknown';
78
+ if (value.includes('anthropic') || value.includes('claude')) return 'anthropic';
79
+ if (value.includes('openai') || value.includes('chatgpt') || value.includes('gpt')) return 'openai';
80
+ if (value.includes('codex')) return 'codex';
81
+ if (value.includes('gemini')) return 'gemini';
82
+ if (value.includes('cursor')) return 'cursor';
83
+ return normalizeToken(value) || 'unknown';
84
+ }
85
+
86
+ function firstObject(...values) {
87
+ return values.find((value) => value && typeof value === 'object' && !Array.isArray(value)) || {};
88
+ }
89
+
90
+ function firstString(...values) {
91
+ for (const value of values) {
92
+ const text = String(value || '').trim();
93
+ if (text) return text;
94
+ }
95
+ return '';
96
+ }
97
+
98
+ function firstNumber(...values) {
99
+ for (const value of values) {
100
+ const number = Number(value);
101
+ if (Number.isFinite(number) && number >= 0) return number;
102
+ }
103
+ return 0;
104
+ }
105
+
106
+ function parseJsonObject(value) {
107
+ if (value && typeof value === 'object' && !Array.isArray(value)) return value;
108
+ if (typeof value !== 'string') return {};
109
+ try {
110
+ const parsed = JSON.parse(value);
111
+ return asObject(parsed);
112
+ } catch {
113
+ return {};
114
+ }
115
+ }
116
+
117
+ function extractAnthropicToolCall(input) {
118
+ const direct = firstObject(input.toolCall, input.toolUse, input.providerToolCall);
119
+ if (direct.name || direct.input) return direct;
120
+
121
+ const content = asArray(input.content);
122
+ return content.find((entry) => entry && entry.type === 'tool_use') || {};
123
+ }
124
+
125
+ function extractOpenAiToolCall(input) {
126
+ const direct = firstObject(input.toolCall, input.functionCall, input.providerToolCall);
127
+ const fn = firstObject(direct.function, input.function);
128
+ if (fn.name || fn.arguments) {
129
+ return {
130
+ id: direct.id,
131
+ name: fn.name,
132
+ arguments: parseJsonObject(fn.arguments),
133
+ };
134
+ }
135
+ if (direct.name || direct.arguments) {
136
+ return {
137
+ id: direct.id,
138
+ name: direct.name,
139
+ arguments: parseJsonObject(direct.arguments),
140
+ };
141
+ }
142
+ return {};
143
+ }
144
+
145
+ function extractMcpToolCall(input) {
146
+ const direct = firstObject(input.mcp, input.mcpToolCall);
147
+ if (direct.name || direct.arguments || direct.uri) return direct;
148
+
149
+ if (MCP_PRIMITIVE_BY_METHOD[input.method]) {
150
+ const params = firstObject(input.params);
151
+ return {
152
+ name: params.name || params.uri,
153
+ arguments: params.arguments,
154
+ server: params.server,
155
+ primitive: MCP_PRIMITIVE_BY_METHOD[input.method],
156
+ uri: params.uri,
157
+ };
158
+ }
159
+
160
+ return {};
161
+ }
162
+
163
+ function extractToolInput(input) {
164
+ const anthropic = extractAnthropicToolCall(input);
165
+ const openai = extractOpenAiToolCall(input);
166
+ const mcp = extractMcpToolCall(input);
167
+ return firstObject(
168
+ input.toolInput,
169
+ input.input,
170
+ input.arguments,
171
+ anthropic.input,
172
+ openai.arguments,
173
+ mcp.arguments,
174
+ input.providerToolCall && input.providerToolCall.input,
175
+ mcp.uri ? { uri: mcp.uri } : {}
176
+ );
177
+ }
178
+
179
+ function extractToolName(input) {
180
+ const anthropic = extractAnthropicToolCall(input);
181
+ const openai = extractOpenAiToolCall(input);
182
+ const mcp = extractMcpToolCall(input);
183
+ return firstString(
184
+ input.toolName,
185
+ input.tool_name,
186
+ input.name,
187
+ anthropic.name,
188
+ openai.name,
189
+ mcp.name,
190
+ input.providerToolCall && input.providerToolCall.name
191
+ );
192
+ }
193
+
194
+ function uniqueStrings(values) {
195
+ return [...new Set(values.map((value) => String(value || '').trim()).filter(Boolean))];
196
+ }
197
+
198
+ function collectAffectedFiles(toolInput = {}, fallback = {}) {
199
+ const files = [
200
+ ...asArray(toolInput.changedFiles),
201
+ ...asArray(toolInput.changed_files),
202
+ ...asArray(toolInput.files),
203
+ ...asArray(toolInput.filePaths),
204
+ ...asArray(toolInput.file_paths),
205
+ ...asArray(toolInput.paths),
206
+ ...asArray(fallback.changedFiles),
207
+ ...asArray(fallback.changed_files),
208
+ toolInput.filePath,
209
+ toolInput.file_path,
210
+ toolInput.path,
211
+ fallback.filePath,
212
+ fallback.file_path,
213
+ fallback.path,
214
+ ];
215
+ return uniqueStrings(files);
216
+ }
217
+
218
+ function inferCommand(toolName, toolInput = {}, fallback = {}) {
219
+ return firstString(
220
+ toolInput.command,
221
+ toolInput.cmd,
222
+ toolInput.shell,
223
+ fallback.command,
224
+ fallback.cmd
225
+ );
226
+ }
227
+
228
+ function inferActionType(toolName, toolInput = {}, fallback = {}) {
229
+ const explicit = firstString(fallback.actionType, toolInput.actionType, toolInput.action_type);
230
+ if (explicit) return explicit;
231
+ if (fallback.method === 'resources/read') return 'context.read';
232
+ if (fallback.method === 'prompts/get') return 'prompt.get';
233
+ const normalizedTool = String(toolName || '').trim();
234
+ const lowerTool = normalizedTool.toLowerCase();
235
+ if (normalizedTool === 'Bash' || containsAny(lowerTool, ['bash', 'shell', 'terminal', 'exec', 'run_command'])) {
236
+ return 'shell.exec';
237
+ }
238
+ if (EDIT_TOOL_NAMES.has(normalizedTool) || containsAny(lowerTool, ['edit', 'write', 'patch', 'file'])) {
239
+ return 'file.write';
240
+ }
241
+ if (containsAny(lowerTool, ['fetch', 'request', 'http', 'browser'])) {
242
+ return 'network.request';
243
+ }
244
+ return 'tool.call';
245
+ }
246
+
247
+ function inferIntent({ actionType, command, affectedFiles }) {
248
+ const text = normalizeCommandText(command);
249
+ if (actionType === 'context.read') return 'read-context';
250
+ if (actionType === 'prompt.get') return 'load-prompt-template';
251
+ if (containsAny(text, ['npm publish', 'yarn publish', 'pnpm publish', 'gh release create'])) {
252
+ return 'publish';
253
+ }
254
+ if (containsAny(text, ['gh pr merge', 'git push'])) {
255
+ return 'release-workflow';
256
+ }
257
+ if (containsAny(text, ['npm test', 'npm run test', 'node test', 'node run test', 'yarn test', 'yarn run test', 'pnpm test', 'pnpm run test', 'pytest', 'go test'])) {
258
+ return 'verify';
259
+ }
260
+ if (actionType === 'file.write' && affectedFiles.length > 0) {
261
+ return 'modify-files';
262
+ }
263
+ return actionType === 'shell.exec' ? 'run-command' : 'use-tool';
264
+ }
265
+
266
+ function normalizeUsage(input = {}, toolInput = {}) {
267
+ const usage = firstObject(input.usage, input.tokenUsage, toolInput.usage);
268
+ const inputTokens = firstNumber(
269
+ usage.input_tokens,
270
+ usage.inputTokens,
271
+ usage.prompt_tokens,
272
+ usage.promptTokens,
273
+ input.inputTokens
274
+ );
275
+ const outputTokens = firstNumber(
276
+ usage.output_tokens,
277
+ usage.outputTokens,
278
+ usage.completion_tokens,
279
+ usage.completionTokens,
280
+ input.outputTokens
281
+ );
282
+ const totalTokens = firstNumber(
283
+ input.tokenEstimate,
284
+ input.estimatedTokens,
285
+ usage.total_tokens,
286
+ usage.totalTokens,
287
+ inputTokens + outputTokens
288
+ );
289
+ return {
290
+ inputTokens,
291
+ outputTokens,
292
+ totalTokens,
293
+ estimatedCostUsd: firstNumber(input.costUsd, input.estimatedCostUsd, usage.costUsd, usage.estimatedCostUsd),
294
+ };
295
+ }
296
+
297
+ function normalizeStringArray(value) {
298
+ if (Array.isArray(value)) return uniqueStrings(value);
299
+ if (typeof value === 'string' && value.trim()) return [value.trim()];
300
+ return [];
301
+ }
302
+
303
+ function firstArray(...values) {
304
+ for (const value of values) {
305
+ if (Array.isArray(value)) return value;
306
+ }
307
+ return [];
308
+ }
309
+
310
+ function normalizeWorkflowPattern(value) {
311
+ const text = normalizeToken(value, '.-');
312
+ if (!text) return '';
313
+ if (['parallel', 'parallelization', 'fanout', 'fan-out'].includes(text)) return 'parallelization';
314
+ if (['chain', 'chaining', 'sequential'].includes(text)) return 'chaining';
315
+ if (['route', 'routing', 'classifier'].includes(text)) return 'routing';
316
+ if (['evaluator', 'optimizer', 'evaluator-optimizer', 'grader'].includes(text)) return 'evaluator-optimizer';
317
+ if (['agent', 'agentic', 'autonomous-agent'].includes(text)) return 'agent';
318
+ if (['workflow', 'single', 'single-action'].includes(text)) return 'single_action';
319
+ return text || '';
320
+ }
321
+
322
+ function inferWorkflowPattern(source = {}, toolInput = {}) {
323
+ const explicit = normalizeWorkflowPattern(firstString(
324
+ source.workflowPattern,
325
+ source.workflow_pattern,
326
+ source.workflow && source.workflow.pattern,
327
+ toolInput.workflowPattern,
328
+ toolInput.workflow_pattern,
329
+ toolInput.workflow && toolInput.workflow.pattern,
330
+ source.pattern,
331
+ toolInput.pattern
332
+ ));
333
+ if (explicit) return explicit;
334
+
335
+ const routes = firstArray(source.routes, toolInput.routes, source.workflow && source.workflow.routes, toolInput.workflow && toolInput.workflow.routes);
336
+ const branches = firstArray(
337
+ source.branches,
338
+ source.subTasks,
339
+ source.subtasks,
340
+ source.parallelBranches,
341
+ toolInput.branches,
342
+ toolInput.subTasks,
343
+ toolInput.subtasks,
344
+ toolInput.parallelBranches,
345
+ source.workflow && source.workflow.branches,
346
+ toolInput.workflow && toolInput.workflow.branches
347
+ );
348
+ const steps = firstArray(source.steps, toolInput.steps, source.workflow && source.workflow.steps, toolInput.workflow && toolInput.workflow.steps);
349
+
350
+ if (source.agent === true || source.isAgent === true || toolInput.agent === true || toolInput.isAgent === true) return 'agent';
351
+ if (routes.length > 0) return 'routing';
352
+ if (branches.length > 1 || source.parallel === true || toolInput.parallel === true) return 'parallelization';
353
+ if (steps.length > 1) return 'chaining';
354
+ if (source.evaluator || source.grader || source.optimizer || toolInput.evaluator || toolInput.grader || toolInput.optimizer) return 'evaluator-optimizer';
355
+ if ((source.goal || toolInput.goal) && firstArray(source.tools, toolInput.tools).length > 0) return 'agent';
356
+ return 'single_action';
357
+ }
358
+
359
+ function hasInspectionEvidence(source = {}, toolInput = {}) {
360
+ const workflow = firstObject(source.workflow, toolInput.workflow);
361
+ const verification = firstObject(source.verification, toolInput.verification, workflow.verification);
362
+ const inspection = firstObject(source.inspection, toolInput.inspection, workflow.inspection);
363
+ const checks = [
364
+ source.observesAfterAction,
365
+ source.observeAfterAction,
366
+ source.readBeforeWrite,
367
+ source.postActionObservation,
368
+ toolInput.observesAfterAction,
369
+ toolInput.observeAfterAction,
370
+ toolInput.readBeforeWrite,
371
+ toolInput.postActionObservation,
372
+ workflow.observesAfterAction,
373
+ workflow.readBeforeWrite,
374
+ verification.required,
375
+ verification.expectedResult,
376
+ verification.command,
377
+ verification.apiResponse,
378
+ verification.screenshot,
379
+ inspection.required,
380
+ inspection.expectedObservation,
381
+ inspection.screenshot,
382
+ inspection.apiResponse,
383
+ ];
384
+ if (checks.some(Boolean)) return true;
385
+ return normalizeStringArray(source.evidence).length > 0
386
+ || normalizeStringArray(toolInput.evidence).length > 0
387
+ || normalizeStringArray(source.checks).length > 0
388
+ || normalizeStringArray(toolInput.checks).length > 0
389
+ || normalizeStringArray(workflow.checks).length > 0;
390
+ }
391
+
392
+ function normalizeWorkflow(source = {}, toolInput = {}) {
393
+ const workflow = firstObject(source.workflow, toolInput.workflow);
394
+ const branches = firstArray(
395
+ source.branches,
396
+ source.subTasks,
397
+ source.subtasks,
398
+ source.parallelBranches,
399
+ toolInput.branches,
400
+ toolInput.subTasks,
401
+ toolInput.subtasks,
402
+ toolInput.parallelBranches,
403
+ workflow.branches,
404
+ workflow.subTasks,
405
+ workflow.subtasks
406
+ );
407
+ const steps = firstArray(source.steps, toolInput.steps, workflow.steps);
408
+ const routes = firstArray(source.routes, toolInput.routes, workflow.routes);
409
+ const tools = firstArray(source.tools, toolInput.tools, workflow.tools);
410
+ const pattern = inferWorkflowPattern(source, toolInput);
411
+ const branchCount = firstNumber(
412
+ source.branchCount,
413
+ toolInput.branchCount,
414
+ workflow.branchCount,
415
+ branches.length
416
+ );
417
+ const stepCount = firstNumber(
418
+ source.stepCount,
419
+ toolInput.stepCount,
420
+ workflow.stepCount,
421
+ steps.length
422
+ );
423
+ const toolCount = firstNumber(
424
+ source.toolCount,
425
+ toolInput.toolCount,
426
+ workflow.toolCount,
427
+ tools.length
428
+ );
429
+ const routeCount = firstNumber(
430
+ source.routeCount,
431
+ toolInput.routeCount,
432
+ workflow.routeCount,
433
+ routes.length
434
+ );
435
+ const inspectionEvidence = hasInspectionEvidence(source, toolInput);
436
+ const requiresInspection = Boolean(
437
+ pattern === 'agent'
438
+ || pattern === 'parallelization'
439
+ || source.requiresInspection
440
+ || toolInput.requiresInspection
441
+ || workflow.requiresInspection
442
+ );
443
+
444
+ return {
445
+ pattern,
446
+ branchCount,
447
+ stepCount,
448
+ toolCount,
449
+ routeCount,
450
+ hasInspectionEvidence: inspectionEvidence,
451
+ requiresInspection,
452
+ isOpenEndedAgent: pattern === 'agent',
453
+ isPredefinedWorkflow: ['single_action', 'chaining', 'routing', 'parallelization', 'evaluator-optimizer'].includes(pattern),
454
+ };
455
+ }
456
+
457
+ function normalizeProviderAction(input = {}) {
458
+ const source = asObject(input);
459
+ const toolInput = extractToolInput(source);
460
+ const toolName = extractToolName(source);
461
+ const command = inferCommand(toolName, toolInput, source);
462
+ const affectedFiles = collectAffectedFiles(toolInput, source);
463
+ const actionType = inferActionType(toolName, toolInput, source);
464
+ const usage = normalizeUsage(source, toolInput);
465
+ const provider = normalizeProvider(firstString(source.provider, source.providerName, source.modelProvider));
466
+ const openai = extractOpenAiToolCall(source);
467
+ const mcp = extractMcpToolCall(source);
468
+ return {
469
+ schemaVersion: 'provider-action-v1',
470
+ provider: mcp.name ? 'mcp' : provider,
471
+ model: firstString(source.model, source.modelName),
472
+ providerCallId: firstString(source.id, source.toolUseId, source.tool_call_id, source.toolCallId, openai.id),
473
+ mcpServer: firstString(source.mcpServer, mcp.server, source.serverName),
474
+ mcpPrimitive: firstString(mcp.primitive, mcp.name ? 'tool' : ''),
475
+ toolName: toolName || (actionType === 'shell.exec' ? 'Bash' : 'Tool'),
476
+ toolInput,
477
+ command,
478
+ actionType,
479
+ intent: inferIntent({ actionType, command, affectedFiles }),
480
+ affectedFiles,
481
+ usage,
482
+ workflow: normalizeWorkflow(source, toolInput),
483
+ rawShape: {
484
+ hasProviderToolCall: Boolean(source.providerToolCall || source.toolCall || source.toolUse),
485
+ hasOpenAiToolCall: Boolean(extractOpenAiToolCall(source).name),
486
+ hasAnthropicToolUse: asArray(source.content).some((entry) => entry && entry.type === 'tool_use'),
487
+ hasMcpToolCall: Boolean(mcp.name || MCP_PRIMITIVE_BY_METHOD[source.method]),
488
+ },
489
+ };
490
+ }
491
+
492
+ function normalizeBudget(input = {}) {
493
+ const budget = asObject(input);
494
+ return {
495
+ maxTokensPerAction: firstNumber(budget.maxTokensPerAction, budget.perActionTokens, budget.tokenLimit),
496
+ remainingTokens: firstNumber(budget.remainingTokens, budget.tokensRemaining),
497
+ maxCostUsdPerAction: firstNumber(budget.maxCostUsdPerAction, budget.perActionCostUsd, budget.costLimitUsd),
498
+ remainingCostUsd: firstNumber(budget.remainingCostUsd, budget.costUsdRemaining),
499
+ maxParallelBranches: firstNumber(budget.maxParallelBranches, budget.parallelBranchLimit, DEFAULT_MAX_PARALLEL_BRANCHES),
500
+ };
501
+ }
502
+
503
+ function buildWorkflowControl(normalizedAction = {}, policyInput = {}) {
504
+ const workflow = asObject(normalizedAction.workflow);
505
+ const policy = asObject(policyInput);
506
+ const reasons = [];
507
+ const warnings = [];
508
+ const maxParallelBranches = firstNumber(
509
+ policy.maxParallelBranches,
510
+ policy.parallelBranchLimit,
511
+ DEFAULT_MAX_PARALLEL_BRANCHES
512
+ );
513
+ const requireInspectionForAgents = policy.requireInspectionForAgents !== false;
514
+
515
+ if (workflow.pattern === 'agent' && requireInspectionForAgents && !workflow.hasInspectionEvidence) {
516
+ reasons.push('Open-ended agent actions must declare environment-inspection or verification evidence before execution.');
517
+ }
518
+ if (workflow.requiresInspection && !workflow.hasInspectionEvidence && workflow.pattern !== 'agent') {
519
+ warnings.push('Workflow declares inspection-sensitive behavior but does not include explicit post-action verification evidence.');
520
+ }
521
+ if (maxParallelBranches > 0 && workflow.branchCount > maxParallelBranches) {
522
+ reasons.push(`Parallel workflow branch count ${workflow.branchCount} exceeds limit ${maxParallelBranches}.`);
523
+ }
524
+
525
+ return {
526
+ mode: reasons.length > 0 ? 'block' : warnings.length > 0 ? 'warn' : 'allow',
527
+ workflow,
528
+ reasons: reasons.concat(warnings),
529
+ policy: {
530
+ maxParallelBranches,
531
+ requireInspectionForAgents,
532
+ },
533
+ };
534
+ }
535
+
536
+ function buildCostControl(normalizedAction = {}, budgetInput = {}) {
537
+ const usage = asObject(normalizedAction.usage);
538
+ const budget = normalizeBudget(budgetInput);
539
+ const reasons = [];
540
+ const totalTokens = firstNumber(usage.totalTokens);
541
+ const estimatedCostUsd = firstNumber(usage.estimatedCostUsd);
542
+
543
+ if (budget.maxTokensPerAction > 0 && totalTokens > budget.maxTokensPerAction) {
544
+ reasons.push(`Token estimate ${totalTokens} exceeds per-action limit ${budget.maxTokensPerAction}.`);
545
+ }
546
+ if (budget.remainingTokens > 0 && totalTokens > budget.remainingTokens) {
547
+ reasons.push(`Token estimate ${totalTokens} exceeds remaining budget ${budget.remainingTokens}.`);
548
+ }
549
+ if (budget.maxCostUsdPerAction > 0 && estimatedCostUsd > budget.maxCostUsdPerAction) {
550
+ reasons.push(`Estimated cost $${estimatedCostUsd.toFixed(4)} exceeds per-action limit $${budget.maxCostUsdPerAction.toFixed(4)}.`);
551
+ }
552
+ if (budget.remainingCostUsd > 0 && estimatedCostUsd > budget.remainingCostUsd) {
553
+ reasons.push(`Estimated cost $${estimatedCostUsd.toFixed(4)} exceeds remaining budget $${budget.remainingCostUsd.toFixed(4)}.`);
554
+ }
555
+ if (budget.maxParallelBranches > 0 && normalizedAction.workflow?.branchCount > budget.maxParallelBranches) {
556
+ reasons.push(`Parallel workflow branch count ${normalizedAction.workflow.branchCount} exceeds budget limit ${budget.maxParallelBranches}.`);
557
+ }
558
+
559
+ const softWarning = reasons.length === 0 && totalTokens >= DEFAULT_SOFT_TOKEN_LIMIT
560
+ ? [`Token estimate ${totalTokens} is above the ${DEFAULT_SOFT_TOKEN_LIMIT} token review threshold.`]
561
+ : [];
562
+ const mode = reasons.length > 0 ? 'block' : softWarning.length > 0 ? 'warn' : 'allow';
563
+
564
+ return {
565
+ mode,
566
+ budget,
567
+ usage: {
568
+ totalTokens,
569
+ inputTokens: firstNumber(usage.inputTokens),
570
+ outputTokens: firstNumber(usage.outputTokens),
571
+ estimatedCostUsd,
572
+ },
573
+ reasons: reasons.concat(softWarning),
574
+ };
575
+ }
576
+
577
+ module.exports = {
578
+ DEFAULT_SOFT_TOKEN_LIMIT,
579
+ buildCostControl,
580
+ buildWorkflowControl,
581
+ normalizeBudget,
582
+ normalizeProvider,
583
+ normalizeProviderAction,
584
+ normalizeWorkflow,
585
+ };