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.
- package/.claude-plugin/marketplace.json +6 -6
- package/.claude-plugin/plugin.json +3 -3
- package/.well-known/llms.txt +5 -5
- package/.well-known/mcp/server-card.json +1 -1
- package/README.md +59 -35
- package/adapters/chatgpt/openapi.yaml +118 -2
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/mcp/server-stdio.js +210 -84
- package/adapters/opencode/opencode.json +1 -1
- package/bench/prompt-eval-suite.json +5 -1
- package/bin/cli.js +157 -8
- package/config/evals/agent-safety-eval.json +338 -22
- package/config/gates/routine.json +43 -0
- package/config/github-about.json +3 -3
- package/config/model-candidates.json +131 -0
- package/openapi/openapi.yaml +118 -2
- package/package.json +55 -48
- package/public/blog.html +7 -7
- package/public/codex-plugin.html +6 -6
- package/public/compare.html +29 -23
- package/public/dashboard.html +82 -10
- package/public/guide.html +28 -28
- package/public/index.html +216 -98
- package/public/learn.html +50 -22
- package/public/lessons.html +1 -1
- package/public/numbers.html +17 -17
- package/public/pro.html +82 -18
- package/scripts/agent-audit-trace.js +55 -0
- package/scripts/agent-memory-lifecycle.js +96 -0
- package/scripts/agent-readiness-plan.js +118 -0
- package/scripts/agentic-data-pipeline.js +21 -1
- package/scripts/agents-sdk-sandbox-plan.js +57 -0
- package/scripts/ai-org-governance.js +98 -0
- package/scripts/ai-search-distribution.js +43 -0
- package/scripts/artifact-agent-plan.js +81 -0
- package/scripts/billing.js +27 -8
- package/scripts/cli-schema.js +18 -2
- package/scripts/code-mode-mcp-plan.js +71 -0
- package/scripts/context-engine.js +1 -2
- package/scripts/context-manager.js +4 -1
- package/scripts/dashboard-render-spec.js +1 -1
- package/scripts/dashboard.js +275 -9
- package/scripts/decision-journal.js +13 -3
- package/scripts/document-workflow-governance.js +62 -0
- package/scripts/enterprise-agent-rollout.js +34 -0
- package/scripts/experience-replay-governance.js +69 -0
- package/scripts/export-hf-dataset.js +1 -1
- package/scripts/feedback-loop.js +92 -4
- package/scripts/feedback-to-rules.js +17 -23
- package/scripts/gates-engine.js +4 -6
- package/scripts/growth-campaigns.js +49 -0
- package/scripts/harness-selector.js +16 -4
- package/scripts/hybrid-supervisor-agent.js +64 -0
- package/scripts/inference-cache-policy.js +72 -0
- package/scripts/inference-economics.js +53 -0
- package/scripts/internal-agent-bootstrap.js +12 -2
- package/scripts/knowledge-layer-plan.js +108 -0
- package/scripts/lesson-inference.js +183 -44
- package/scripts/lesson-search.js +4 -1
- package/scripts/llm-client.js +157 -26
- package/scripts/mailer/resend-mailer.js +112 -1
- package/scripts/mcp-transport-strategy.js +66 -0
- package/scripts/memory-store-governance.js +60 -0
- package/scripts/meta-agent-loop.js +7 -13
- package/scripts/model-access-eligibility.js +38 -0
- package/scripts/model-migration-readiness.js +55 -0
- package/scripts/operational-integrity.js +96 -3
- package/scripts/otel-declarative-config.js +56 -0
- package/scripts/perplexity-client.js +1 -1
- package/scripts/post-training-governance.js +34 -0
- package/scripts/private-core-boundary.js +72 -0
- package/scripts/production-agent-readiness.js +40 -0
- package/scripts/prompt-eval.js +564 -32
- package/scripts/prompt-programs.js +93 -0
- package/scripts/provider-action-normalizer.js +585 -0
- package/scripts/scaling-law-claims.js +60 -0
- package/scripts/security-scanner.js +1 -1
- package/scripts/self-distill-agent.js +7 -32
- package/scripts/seo-gsd.js +232 -55
- package/scripts/skill-rag-router.js +53 -0
- package/scripts/spec-gate.js +1 -1
- package/scripts/student-consistent-training.js +73 -0
- package/scripts/synthetic-data-provenance.js +98 -0
- package/scripts/task-context-result.js +81 -0
- package/scripts/telemetry-analytics.js +149 -0
- package/scripts/thompson-sampling.js +2 -2
- package/scripts/token-savings.js +7 -6
- package/scripts/token-tco.js +46 -0
- package/scripts/tool-registry.js +63 -3
- package/scripts/verification-loop.js +10 -1
- package/scripts/verifier-scoring.js +71 -0
- package/scripts/workflow-sentinel.js +284 -28
- package/scripts/workspace-agent-routines.js +118 -0
- package/src/api/server.js +381 -120
- package/scripts/analytics-report.js +0 -328
- package/scripts/autonomous-workflow.js +0 -377
- package/scripts/billing-setup.js +0 -109
- package/scripts/creator-campaigns.js +0 -239
- package/scripts/cross-encoder-reranker.js +0 -235
- package/scripts/daemon-manager.js +0 -108
- package/scripts/decision-trace.js +0 -354
- package/scripts/delegation-runtime.js +0 -896
- package/scripts/dispatch-brief.js +0 -159
- package/scripts/distribution-surfaces.js +0 -110
- package/scripts/feedback-history-distiller.js +0 -382
- package/scripts/funnel-analytics.js +0 -35
- package/scripts/history-distiller.js +0 -200
- package/scripts/hosted-job-launcher.js +0 -256
- package/scripts/intent-router.js +0 -392
- package/scripts/lesson-reranker.js +0 -263
- package/scripts/lesson-retrieval.js +0 -148
- package/scripts/managed-lesson-agent.js +0 -183
- package/scripts/operational-dashboard.js +0 -103
- package/scripts/operational-summary.js +0 -129
- package/scripts/operator-artifacts.js +0 -608
- package/scripts/optimize-context.js +0 -17
- package/scripts/org-dashboard.js +0 -206
- package/scripts/partner-orchestration.js +0 -146
- package/scripts/predictive-insights.js +0 -356
- package/scripts/pulse.js +0 -80
- package/scripts/reflector-agent.js +0 -221
- package/scripts/sales-pipeline.js +0 -681
- package/scripts/session-episode-store.js +0 -329
- package/scripts/session-health-sensor.js +0 -242
- package/scripts/session-report.js +0 -120
- package/scripts/swarm-coordinator.js +0 -81
- package/scripts/tool-kpi-tracker.js +0 -12
- package/scripts/webhook-delivery.js +0 -62
- 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
|
+
};
|