thumbgate 1.14.1 → 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 (150) 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 +60 -35
  6. package/adapters/chatgpt/openapi.yaml +118 -2
  7. package/adapters/claude/.mcp.json +2 -2
  8. package/adapters/mcp/server-stdio.js +217 -84
  9. package/adapters/opencode/opencode.json +1 -1
  10. package/bench/prompt-eval-suite.json +5 -1
  11. package/bin/cli.js +211 -8
  12. package/config/enforcement.json +59 -7
  13. package/config/evals/agent-safety-eval.json +338 -22
  14. package/config/gates/default.json +33 -0
  15. package/config/gates/routine.json +43 -0
  16. package/config/github-about.json +3 -3
  17. package/config/mcp-allowlists.json +4 -0
  18. package/config/merge-quality-checks.json +2 -1
  19. package/config/model-candidates.json +131 -0
  20. package/openapi/openapi.yaml +118 -2
  21. package/package.json +70 -51
  22. package/public/blog.html +7 -7
  23. package/public/codex-plugin.html +13 -7
  24. package/public/compare.html +29 -23
  25. package/public/dashboard.html +105 -12
  26. package/public/guide.html +28 -28
  27. package/public/index.html +233 -97
  28. package/public/learn.html +87 -20
  29. package/public/lessons.html +26 -2
  30. package/public/numbers.html +271 -0
  31. package/public/pro.html +89 -19
  32. package/scripts/agent-audit-trace.js +55 -0
  33. package/scripts/agent-memory-lifecycle.js +96 -0
  34. package/scripts/agent-readiness-plan.js +118 -0
  35. package/scripts/agentic-data-pipeline.js +21 -1
  36. package/scripts/agents-sdk-sandbox-plan.js +57 -0
  37. package/scripts/ai-org-governance.js +98 -0
  38. package/scripts/ai-search-distribution.js +43 -0
  39. package/scripts/artifact-agent-plan.js +81 -0
  40. package/scripts/billing.js +27 -8
  41. package/scripts/cli-feedback.js +2 -1
  42. package/scripts/cli-schema.js +60 -5
  43. package/scripts/code-mode-mcp-plan.js +71 -0
  44. package/scripts/commercial-offer.js +1 -1
  45. package/scripts/context-engine.js +1 -2
  46. package/scripts/context-manager.js +4 -1
  47. package/scripts/contextfs.js +214 -32
  48. package/scripts/dashboard-render-spec.js +1 -1
  49. package/scripts/dashboard.js +275 -9
  50. package/scripts/decision-journal.js +13 -3
  51. package/scripts/document-workflow-governance.js +62 -0
  52. package/scripts/enterprise-agent-rollout.js +34 -0
  53. package/scripts/experience-replay-governance.js +69 -0
  54. package/scripts/export-hf-dataset.js +1 -1
  55. package/scripts/feedback-loop.js +141 -9
  56. package/scripts/feedback-to-rules.js +17 -23
  57. package/scripts/gates-engine.js +4 -6
  58. package/scripts/growth-campaigns.js +49 -0
  59. package/scripts/harness-selector.js +145 -1
  60. package/scripts/hybrid-supervisor-agent.js +64 -0
  61. package/scripts/inference-cache-policy.js +72 -0
  62. package/scripts/inference-economics.js +53 -0
  63. package/scripts/internal-agent-bootstrap.js +12 -2
  64. package/scripts/knowledge-layer-plan.js +108 -0
  65. package/scripts/lesson-canonical.js +181 -0
  66. package/scripts/lesson-db.js +71 -10
  67. package/scripts/lesson-inference.js +183 -44
  68. package/scripts/lesson-search.js +4 -1
  69. package/scripts/lesson-synthesis.js +23 -2
  70. package/scripts/llm-client.js +157 -26
  71. package/scripts/mailer/resend-mailer.js +112 -1
  72. package/scripts/mcp-transport-strategy.js +66 -0
  73. package/scripts/memory-store-governance.js +60 -0
  74. package/scripts/meta-agent-loop.js +7 -13
  75. package/scripts/model-access-eligibility.js +38 -0
  76. package/scripts/model-migration-readiness.js +55 -0
  77. package/scripts/native-messaging-audit.js +514 -0
  78. package/scripts/operational-integrity.js +96 -3
  79. package/scripts/otel-declarative-config.js +56 -0
  80. package/scripts/perplexity-client.js +1 -1
  81. package/scripts/post-training-governance.js +34 -0
  82. package/scripts/pr-manager.js +47 -7
  83. package/scripts/private-core-boundary.js +72 -0
  84. package/scripts/production-agent-readiness.js +40 -0
  85. package/scripts/profile-router.js +16 -1
  86. package/scripts/prompt-eval.js +564 -32
  87. package/scripts/prompt-programs.js +93 -0
  88. package/scripts/provider-action-normalizer.js +585 -0
  89. package/scripts/rule-validator.js +285 -0
  90. package/scripts/scaling-law-claims.js +60 -0
  91. package/scripts/security-scanner.js +1 -1
  92. package/scripts/self-distill-agent.js +7 -32
  93. package/scripts/seo-gsd.js +400 -43
  94. package/scripts/skill-rag-router.js +53 -0
  95. package/scripts/spec-gate.js +1 -1
  96. package/scripts/student-consistent-training.js +73 -0
  97. package/scripts/synthetic-data-provenance.js +98 -0
  98. package/scripts/task-context-result.js +81 -0
  99. package/scripts/telemetry-analytics.js +149 -0
  100. package/scripts/thompson-sampling.js +2 -2
  101. package/scripts/token-savings.js +7 -6
  102. package/scripts/token-tco.js +46 -0
  103. package/scripts/tool-registry.js +75 -3
  104. package/scripts/verification-loop.js +10 -1
  105. package/scripts/verifier-scoring.js +71 -0
  106. package/scripts/workflow-sentinel.js +284 -28
  107. package/scripts/workspace-agent-routines.js +118 -0
  108. package/skills/thumbgate/SKILL.md +1 -1
  109. package/src/api/server.js +434 -120
  110. package/.claude-plugin/README.md +0 -170
  111. package/adapters/README.md +0 -12
  112. package/scripts/analytics-report.js +0 -328
  113. package/scripts/autonomous-workflow.js +0 -377
  114. package/scripts/billing-setup.js +0 -109
  115. package/scripts/creator-campaigns.js +0 -239
  116. package/scripts/cross-encoder-reranker.js +0 -235
  117. package/scripts/daemon-manager.js +0 -108
  118. package/scripts/decision-trace.js +0 -354
  119. package/scripts/delegation-runtime.js +0 -896
  120. package/scripts/dispatch-brief.js +0 -159
  121. package/scripts/distribution-surfaces.js +0 -110
  122. package/scripts/feedback-history-distiller.js +0 -382
  123. package/scripts/funnel-analytics.js +0 -35
  124. package/scripts/history-distiller.js +0 -200
  125. package/scripts/hosted-job-launcher.js +0 -256
  126. package/scripts/intent-router.js +0 -392
  127. package/scripts/lesson-reranker.js +0 -263
  128. package/scripts/lesson-retrieval.js +0 -148
  129. package/scripts/managed-lesson-agent.js +0 -183
  130. package/scripts/operational-dashboard.js +0 -103
  131. package/scripts/operational-summary.js +0 -129
  132. package/scripts/operator-artifacts.js +0 -608
  133. package/scripts/optimize-context.js +0 -17
  134. package/scripts/org-dashboard.js +0 -206
  135. package/scripts/partner-orchestration.js +0 -146
  136. package/scripts/predictive-insights.js +0 -356
  137. package/scripts/pulse.js +0 -80
  138. package/scripts/reflector-agent.js +0 -221
  139. package/scripts/sales-pipeline.js +0 -681
  140. package/scripts/session-episode-store.js +0 -329
  141. package/scripts/session-health-sensor.js +0 -242
  142. package/scripts/session-report.js +0 -120
  143. package/scripts/swarm-coordinator.js +0 -81
  144. package/scripts/tool-kpi-tracker.js +0 -12
  145. package/scripts/webhook-delivery.js +0 -62
  146. package/scripts/workflow-sprint-intake.js +0 -475
  147. package/skills/agent-memory/SKILL.md +0 -97
  148. package/skills/solve-architecture-autonomy/SKILL.md +0 -17
  149. package/skills/solve-architecture-autonomy/tool.js +0 -33
  150. package/skills/thumbgate-feedback/SKILL.md +0 -49
@@ -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
+ };