eminiar 0.12.0 → 0.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/README.md +56 -16
- package/bin/_resolve.sh +7 -4
- package/bin/em-hook +2 -1
- package/bin/em-server +2 -1
- package/bin/eminiar +2 -1
- package/dist/cli.js +33984 -20959
- package/dist/hook-async-J3UKDO62.js +15 -0
- package/dist/hook-chunk-BECSRJFD.js +24114 -0
- package/dist/hook-chunk-DSLVDGOB.js +9888 -0
- package/dist/hook-chunk-RYZAFUTT.js +3780 -0
- package/dist/hook-hooks-IDTGXCFD.js +1829 -0
- package/dist/hook.js +713 -32688
- package/dist/server.js +54678 -47123
- package/dist/ui/app.global.js +75 -69
- package/dist/ui/styles.css +4887 -853
- package/eminiar.config.schema.json +450 -1
- package/package.json +2 -2
- package/skills/em-doctor/SKILL.md +14 -1
- package/skills/em-onboard/SKILL.md +33 -1
|
@@ -0,0 +1,3780 @@
|
|
|
1
|
+
import { createRequire as __emCreateRequire } from "node:module"; const require = __emCreateRequire(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
AdjudicatorConfigSchema,
|
|
4
|
+
DEFAULT_ROLES,
|
|
5
|
+
SCORER_VERSION,
|
|
6
|
+
SURFACES,
|
|
7
|
+
TIERS,
|
|
8
|
+
TOOL_IDS,
|
|
9
|
+
classifyPrompt,
|
|
10
|
+
external_exports,
|
|
11
|
+
getContext,
|
|
12
|
+
getStorage,
|
|
13
|
+
init_esm_shims,
|
|
14
|
+
loadConfig,
|
|
15
|
+
paths
|
|
16
|
+
} from "./hook-chunk-BECSRJFD.js";
|
|
17
|
+
|
|
18
|
+
// src/adjudicator/async.ts
|
|
19
|
+
init_esm_shims();
|
|
20
|
+
import { spawn as nodeSpawn } from "child_process";
|
|
21
|
+
|
|
22
|
+
// src/adjudicator/engine.ts
|
|
23
|
+
init_esm_shims();
|
|
24
|
+
import { createHash as createHash3 } from "crypto";
|
|
25
|
+
|
|
26
|
+
// src/adjudicator/blend.ts
|
|
27
|
+
init_esm_shims();
|
|
28
|
+
|
|
29
|
+
// src/adjudicator/heuristic.ts
|
|
30
|
+
init_esm_shims();
|
|
31
|
+
|
|
32
|
+
// src/adjudicator/features.ts
|
|
33
|
+
init_esm_shims();
|
|
34
|
+
var p = (intent, re, w = 1) => ({ intent, re, w });
|
|
35
|
+
var KEBAB_NAME = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)+$/;
|
|
36
|
+
function isPackageName(name, libraries) {
|
|
37
|
+
return !libraries || !KEBAB_NAME.test(name) || libraries.includes(name);
|
|
38
|
+
}
|
|
39
|
+
var PKG = String.raw`(?![\w.-]*\.(?:ts|tsx|js|jsx|mjs|cjs|py|go|rs|rb|php|java|md|json|ya?ml|toml|sql|sh|css|html)\b)(?:@[a-z0-9][\w.-]*\/[a-z0-9][\w.-]*|node:[a-z_]+|[a-z][a-z0-9]*(?:[-.][a-z0-9]+)+|[a-z]{2,}\d+[a-z]*)(?![\w./-])`;
|
|
40
|
+
var DET = "(?:the|a|an|its|their|our|this|that|these|those|my)";
|
|
41
|
+
var CLOSED = "(?:when|if|after|before|while|until|unless|once|on|at|in|into|onto|from|to|with|without|for|by|of|about|and|or|but|so|then|is|are|was|were|be|been|does|do|did|doesn't|don't|isn't|aren't|should|would|will|can|could|must|may|might|has|have|had|not|never)";
|
|
42
|
+
var NP_WORD = String.raw`(?!${CLOSED}\b)[\w@./:+-]+`;
|
|
43
|
+
var DOC_HEAD = String.raw`(?:docs?|documentation|readme|release notes|changelog|api reference|reference|manual|wiki|site|website|(?<!\b(?:next|previous|prev|first|last|each|every|another) )pages?(?! of\b))(?=\s*(?:$|[,.;:!?)])|\s+${CLOSED}\b)`;
|
|
44
|
+
var FETCH_OBJECT = String.raw`(?:${DET} )?(?:${NP_WORD} ){0,3}(?:https?:\/\/|${DOC_HEAD})|(?:${DET} )?${PKG}(?=\s*(?:$|[,.;:!?]|v?\d))|the latest\b`;
|
|
45
|
+
var FETCH_VERB = "(?:fetch|download|crawl|scrape)";
|
|
46
|
+
var WRITTEN_DOC = String.raw`(?:${DET} )?(?:docs|readme|documentation|changelog|release notes|help text|(?:[\w@./:+-]+ ){0,2}(?:docs?|documentation|reference|manual|man|help|wiki|api|readme|changelog) (?:page|site|section|entry|article)s?)\b`;
|
|
47
|
+
var EDIT_DOC_VERB = "(?:update|edit|fix|tweak|reword|touch up|expand|remove|correct|clean up)";
|
|
48
|
+
var WRITE_DOC_VERB = "(?:add|write|draft|create)";
|
|
49
|
+
var REWRITE_DOC_VERB = "(?:rewrite|restructure|reorgani[sz]e)";
|
|
50
|
+
var DOC_WRITE = new RegExp(
|
|
51
|
+
String.raw`\b(?:${EDIT_DOC_VERB}|${WRITE_DOC_VERB}|${REWRITE_DOC_VERB}) ${WRITTEN_DOC}`,
|
|
52
|
+
"g"
|
|
53
|
+
);
|
|
54
|
+
var FORMATTER = "(?:biome|prettier|dprint|ruff|black|gofmt|rustfmt|clang-format)";
|
|
55
|
+
var UI_NOUN = "(?:view|page|dashboard|panel|tab|sidebar|chart|ui|screen|modal|dialog|card|header|footer|toolbar|menu|tooltip|legend|badge|widget|form|layout|status ?bar|status ?line)";
|
|
56
|
+
var UI_PLACE = `${UI_NOUN}s?`;
|
|
57
|
+
var PHRASES = [
|
|
58
|
+
// impact: judging the effect of a change is reasoning; listing dependents is a graph lookup
|
|
59
|
+
p("impact", /\bwhat (?:would|will|could|might) (?:break|change|fail)\b/),
|
|
60
|
+
p("impact", /\bwhat breaks\b/),
|
|
61
|
+
p("impact", /\b(?:blast radius|impact of|affected by)\b/),
|
|
62
|
+
p("impact", /\b(?:what|who|which \w+) (?:else )?(?:depends? on|imports?|calls?|uses?)\b/, 0.5),
|
|
63
|
+
p(
|
|
64
|
+
"impact",
|
|
65
|
+
/\b(?:files?|modules?|code|callers?|places?) (?:that|which) (?:imports?|uses?|calls?|references?)\b/,
|
|
66
|
+
0.5
|
|
67
|
+
),
|
|
68
|
+
p("impact", /\b(?:callers|usages?|references|dependents|consumers) of\b/, 0.5),
|
|
69
|
+
// refactor: migrations and platform moves touch every module that uses the old thing
|
|
70
|
+
p("refactor", /\b(?:esm|cjs|commonjs|typescript|ts|async|python ?3)[- ]only\b/),
|
|
71
|
+
p(
|
|
72
|
+
"refactor",
|
|
73
|
+
/\bdrop (?:the |our )?(?:[\w.-]+ ){0,2}(?:build|support|dependency|runtime|fallback)\b/
|
|
74
|
+
),
|
|
75
|
+
p(
|
|
76
|
+
"refactor",
|
|
77
|
+
/\bupgrade (?:[\w.-]+ ){0,3}to (?:the )?(?:next |latest )?(?:major|v?\d+(?:\.x)?)\b/
|
|
78
|
+
),
|
|
79
|
+
p(
|
|
80
|
+
"refactor",
|
|
81
|
+
/\b(?:major (?:version )?(?:upgrade|bump)|bump (?:\w+ ){0,3}to (?:the )?next major)\b/
|
|
82
|
+
),
|
|
83
|
+
// "from X to Y" between named things, not numbers ("change the port from 3000 to 8080")
|
|
84
|
+
p("refactor", /\b(?:port|move|switch|migrate) (?:[\w./-]+ ){0,5}from [a-z][\w./:-]* to\b/),
|
|
85
|
+
p("refactor", /^\s*port (?:[\w./-]+ ){0,5}to (?=[a-z])/),
|
|
86
|
+
p("refactor", /\breplace (?:[\w./-]+ ){0,5}with\b/),
|
|
87
|
+
// An extraction into a function or module is bounded (like "extract"), not a sweeping refactor.
|
|
88
|
+
p(
|
|
89
|
+
"implement",
|
|
90
|
+
/\brefactor (?:[\w./'-]+ ){1,8}into (?:a |an |one |its own |their own |separate |shared )?(?:[\w-]+ ){0,2}(?:function|helper|method|hook|module|class|component|utility|util)s?\b/
|
|
91
|
+
),
|
|
92
|
+
// history
|
|
93
|
+
p("history", /\bwhy (?:was|were|did)\b(?! we\b)/),
|
|
94
|
+
p("history", /\bwhen (?:was|were|did)\b/),
|
|
95
|
+
p("history", /\b(?:git (?:blame|log|show|history)|commit history|check (?:the )?history)\b/),
|
|
96
|
+
p("history", /\bwho (?:wrote|changed|added|removed)\b/),
|
|
97
|
+
// A git verb with commits as its object is doing git; commits as the subject of a question are history.
|
|
98
|
+
p(
|
|
99
|
+
"git",
|
|
100
|
+
/\b(?:amend|squash|revert|reword|undo|reset|drop|cherry-pick|rebase|push) (?:the |my |our |these |those )?(?:last |previous |latest |recent )?(?:\w+ )?commits?\b/
|
|
101
|
+
),
|
|
102
|
+
p("history", /\b(?:which|what|whose|last|recent|latest|previous|past) (?:\w+ ){0,2}commits?\b/),
|
|
103
|
+
p("history", /\bcommits? (?:that|which|touching)\b|\bcommits (?:on|in|since|from|by|between)\b/),
|
|
104
|
+
p(
|
|
105
|
+
"history",
|
|
106
|
+
/\b(?:later|subsequently|eventually) (?:needed|required|got|had|took) (?:a |an |to be )?(?:fix|revert|patch|hotfix)\w*|\b(?:riskiest|most (?:risky|fragile|churned)|(?:bug|fix)-prone)\b/
|
|
107
|
+
),
|
|
108
|
+
// recall (judgement against past decisions); before "why did" style history
|
|
109
|
+
p(
|
|
110
|
+
"recall",
|
|
111
|
+
/\b(?:should we|is it safe|is it ok(?:ay)?|safe to|did we (?:decide|agree|choose|pick)|what did we decide|why did we)\b/
|
|
112
|
+
),
|
|
113
|
+
// run
|
|
114
|
+
p("run", /\b(?:the )?test suite\b/),
|
|
115
|
+
p("run", /\brun (?:the |all (?:the )?)?tests?\b/),
|
|
116
|
+
p("run", /\bnpm (?:audit|run \w+|test|ci)\b/),
|
|
117
|
+
p("run", /\btype ?check(?:s|ing)?\b/),
|
|
118
|
+
// bigfile
|
|
119
|
+
p("bigfile", /\b\d[\d,.]*k?[- ]?(?:lines?|loc)\b/),
|
|
120
|
+
p("bigfile", /\b(?:whole|entire|full) file\b/),
|
|
121
|
+
// writing docs is an edit, not a docs lookup; it runs before the docs phrases so a doc that is
|
|
122
|
+
// the object of a write verb ("update the api page", "write release notes for v2") is masked
|
|
123
|
+
p(
|
|
124
|
+
"edit_small",
|
|
125
|
+
new RegExp(String.raw`\b${EDIT_DOC_VERB} (?:(?:the |our |a )?comments?\b|${WRITTEN_DOC})`)
|
|
126
|
+
),
|
|
127
|
+
p("implement", new RegExp(String.raw`\b${WRITE_DOC_VERB} ${WRITTEN_DOC}`), 0.8),
|
|
128
|
+
p("refactor", new RegExp(String.raw`\b${REWRITE_DOC_VERB} ${WRITTEN_DOC}`)),
|
|
129
|
+
// docs: a fetch verb leads only with documentation to fetch as its object
|
|
130
|
+
p("docs", new RegExp(String.raw`^\s*${FETCH_VERB} (?=${FETCH_OBJECT})`), 0.7),
|
|
131
|
+
// docs: reading documentation of something else
|
|
132
|
+
p("docs", /\b(?:docs?|documentation|readme) (?:for|on|about|of)\b/),
|
|
133
|
+
p("docs", /\b(?:latest|current|newest) version\b/),
|
|
134
|
+
p("docs", /\b(?:api reference|release notes|changelog for)\b/),
|
|
135
|
+
p("edit_small", /\bfix (?:the |a )?typos?\b/),
|
|
136
|
+
// adding a comment or annotation is mechanical, not new behaviour
|
|
137
|
+
p(
|
|
138
|
+
"edit_small",
|
|
139
|
+
/\badd (?:a |an |some )?(?:short |brief |one-line |small )?(?:comments?|docstrings?|jsdoc|log lines?|type annotations?|todo)\b/
|
|
140
|
+
),
|
|
141
|
+
// docs: documentation named as the source (after writing docs, which is an edit)
|
|
142
|
+
p(
|
|
143
|
+
"docs",
|
|
144
|
+
/\b(?:docs?|documentation|reference|manual|man|help|wiki|api) (?:page|pages|site|section|entry|article)\b/
|
|
145
|
+
),
|
|
146
|
+
p(
|
|
147
|
+
"docs",
|
|
148
|
+
/\b(?:read|check|see|consult|search|open|browse|skim|look (?:at|in|through)|go through) (?:the |its |their |our )?(?:[\w@./:+-]+ ){0,3}(?:docs|documentation|api reference|reference docs|manual)\b/
|
|
149
|
+
),
|
|
150
|
+
p(
|
|
151
|
+
"docs",
|
|
152
|
+
/\b(?:what|how) (?:do|does) (?:the |its |their )?(?:[\w@./:+-]+ ){0,2}(?:docs|documentation|readme|spec|reference) (?:say|recommend|suggest|describe|mention|cover|list)\b|\baccording to (?:the |its |their )?(?:[\w@./:+-]+ ){0,2}(?:docs|documentation|spec|readme)\b/
|
|
153
|
+
),
|
|
154
|
+
// docs: how a third-party package (named like one, never a file) behaves; a kebab-case name
|
|
155
|
+
// that is not a known library is the project's own ("how does agent-pin pick the model")
|
|
156
|
+
{
|
|
157
|
+
...p(
|
|
158
|
+
"docs",
|
|
159
|
+
new RegExp(
|
|
160
|
+
String.raw`\bhow (?:does|do|can|should|would|will) (${PKG})(?= (?![a-z]+ing\b)[a-z])`
|
|
161
|
+
)
|
|
162
|
+
),
|
|
163
|
+
pkg: true
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
...p(
|
|
167
|
+
"docs",
|
|
168
|
+
new RegExp(
|
|
169
|
+
String.raw`\b(?:does|do|can|will) (${PKG}) (?:support|handle|allow|provide|offer|expose|have|ship|include|accept|work with)\b`
|
|
170
|
+
)
|
|
171
|
+
),
|
|
172
|
+
pkg: true
|
|
173
|
+
},
|
|
174
|
+
// formatting as an action (a format, or the formatting of something, is a noun): the lead
|
|
175
|
+
// takes code to format as its object (a determiner, a file or dir, code, src, changed or staged
|
|
176
|
+
// files) or names the formatter ("format X with biome"); "format code blocks" is a noun phrase
|
|
177
|
+
p(
|
|
178
|
+
"edit_small",
|
|
179
|
+
new RegExp(
|
|
180
|
+
String.raw`^\s*format (?=(?:the|this|these|those|all|our|my|it|them|everything|code(?! ?(?:blocks?|snippets?|spans?|fences?)\b)|src|files?|changed|staged|modified)\b|[\w./-]+\.[a-z]{1,4}\b|[\w.-]+\/|(?:[\w./-]+ ){0,3}with ${FORMATTER}\b)`
|
|
181
|
+
)
|
|
182
|
+
),
|
|
183
|
+
p(
|
|
184
|
+
"edit_small",
|
|
185
|
+
new RegExp(
|
|
186
|
+
String.raw`\b(?:apply|run|fix|check|redo|enforce|use) (?:the |our |a |all )?(?:[\w-]+ )?(?:formatting|formatter)\b|\b${FORMATTER} (?:format|fmt|--write)\b`
|
|
187
|
+
)
|
|
188
|
+
),
|
|
189
|
+
// ui: showing something in a UI surface builds it ("show me X" is a lookup); so does leaving
|
|
190
|
+
// something out of what a view, chart or report counts
|
|
191
|
+
p(
|
|
192
|
+
"implement",
|
|
193
|
+
new RegExp(
|
|
194
|
+
String.raw`\b(?:show|display|render|surface|highlight)(?= (?!me\b|us\b|you\b|how\b|what\b|where\b|which\b|why\b)(?:[\w'.-]+ ){1,8}(?:in|on|inside|within|under|next to|beside) (?:the |a |an |each |every |our |its |their )?(?:[\w-]+ ){0,3}${UI_PLACE}\b)`
|
|
195
|
+
)
|
|
196
|
+
),
|
|
197
|
+
p(
|
|
198
|
+
"implement",
|
|
199
|
+
new RegExp(
|
|
200
|
+
String.raw`\b(?:exclude|hide|filter out|omit|leave out)(?= (?:[\w'.-]+ ){0,4}(?:from|in|on) (?:the |a |an |each |every |our |its |their )?(?:[\w-]+ ){0,3}(?:${UI_PLACE}|(?:lists?|tables?|totals?|counts?|rates?|metrics?|stats|summary|summaries|results|reports?|exports?))\b)`
|
|
201
|
+
)
|
|
202
|
+
),
|
|
203
|
+
// plan: the verb, or a plan to produce; never a modifier ("plan lines") or part of a name ("plan-advice")
|
|
204
|
+
p("plan", /^\s*plan(?=\s)/, 0.9),
|
|
205
|
+
p(
|
|
206
|
+
"plan",
|
|
207
|
+
/\bplan (?:the|a|an|out|how|for|our|this|these|its|it|them|which|what|ahead|and)\b/,
|
|
208
|
+
0.63
|
|
209
|
+
),
|
|
210
|
+
p(
|
|
211
|
+
"plan",
|
|
212
|
+
/\b(?:a|an|(?:make|write|draft|create|prepare|produce|need|propose|outline) (?:the|me a|us a)) (?:[\w-]+ ){0,2}plan\b(?![-./]\w)/,
|
|
213
|
+
0.63
|
|
214
|
+
),
|
|
215
|
+
p("plan", /\b(?:split|break|divide) (?:\w+ ){0,3}(?:into|down)\b/),
|
|
216
|
+
p(
|
|
217
|
+
"plan",
|
|
218
|
+
/\b(?:into (?:sub)?tasks|assign (?:\w+ )?(?:workers|tasks|agents|owners)|roll ?out|work breakdown)\b/
|
|
219
|
+
),
|
|
220
|
+
// review
|
|
221
|
+
p("review", /\b(?:security|code|design) (?:review|audit)\b/),
|
|
222
|
+
p("review", /\breview (?:this|the|my|our) (?:pr|diff|change|patch|branch)\b/),
|
|
223
|
+
// test
|
|
224
|
+
p("test", /\b(?:unit|regression|integration|e2e|end-to-end|snapshot) tests?\b/),
|
|
225
|
+
p("test", /\b(?:test coverage|write (?:a |some )?tests?)\b/),
|
|
226
|
+
// git: doing git (commit, push, rebase, branch off), not a branch or commits as a subject; a
|
|
227
|
+
// git subcommand, not git as a noun ("the git root", "git hooks"); a named branch ("switch to
|
|
228
|
+
// the main branch"), not a control-flow one ("make the error branch return null", "the branch
|
|
229
|
+
// variable", "a branch of the decision tree", "a branch in the switch")
|
|
230
|
+
p(
|
|
231
|
+
"git",
|
|
232
|
+
/\b(?:commit|push|stash|rebase) (?:these|the|this|my|all|our|it|them|everything|those|your|changes)\b/
|
|
233
|
+
),
|
|
234
|
+
p(
|
|
235
|
+
"git",
|
|
236
|
+
/\b(?:git (?:add|am|apply|bisect|branch|checkout|cherry-pick|clean|clone|commit|config|describe|diff|fetch|gc|grep|init|merge|mv|pull|push|rebase|reflog|remote|reset|restore|revert|rm|shortlog|stash|status|submodule|switch|tag)|open a (?:pr|pull request)|cherry-pick\w*|force[- ]push)\b/
|
|
237
|
+
),
|
|
238
|
+
p(
|
|
239
|
+
"git",
|
|
240
|
+
/\b(?:create|cut|make|open|start|switch to|check ?out|delete|rename|new) (?:a |the |new |this |that |my )*(?:(?!(?:if|else|error|catch|default|fallback|true|false|happy|dead|case)\b)[\w./-]+ )?branch(?:es)?\b(?! (?:variable|var|logic|condition|statement|coverage|details)\b| (?:of|in|inside|within) (?!(?:the |this |our |my )?(?:git|repo|repository|remote|origin|github|main|master|develop|trunk)\b))|\bbranch (?:off|from|out)\b/
|
|
241
|
+
),
|
|
242
|
+
p(
|
|
243
|
+
"git",
|
|
244
|
+
/\b(?:merge|rebase) (?:\S+ )?(?:into|onto)\b|\b(?:merge|rebase) (?:main|master|develop|the branch)\b|\bresolve (?:the |these |all )?(?:merge )?conflicts?\b/
|
|
245
|
+
),
|
|
246
|
+
// debug: symptoms
|
|
247
|
+
p(
|
|
248
|
+
"debug",
|
|
249
|
+
/\b(?:does ?n[o']?t work|not working|is (?:too )?slow|returns? (?:duplicate|wrong|empty|stale)|stack ?trace)\b/
|
|
250
|
+
),
|
|
251
|
+
p("debug", /\bwhy (?:is|are|does|do|doesn't|isn't|would)\b/),
|
|
252
|
+
// design
|
|
253
|
+
p("design", /\b(?:design (?:a|an|the)|architecture (?:for|of))\b/),
|
|
254
|
+
// research
|
|
255
|
+
p("research", /\b(?:trade-?offs?|alternatives to|pros and cons)\b/),
|
|
256
|
+
// lookup
|
|
257
|
+
p("lookup", /\b(?:how does|how do|tell me|what is|what are|where is|where are)\b/, 0.7)
|
|
258
|
+
];
|
|
259
|
+
var STEMS = [
|
|
260
|
+
p(
|
|
261
|
+
"lookup",
|
|
262
|
+
/\b(?:what|where|which|show|list|explain|find|search|summari[sz]e|describe|locate)\b/,
|
|
263
|
+
0.6
|
|
264
|
+
),
|
|
265
|
+
p("impact", /\b(?:depend\w*|affect\w*|impact\w*)\b/, 0.8),
|
|
266
|
+
p("history", /\b(?:blame|history|histor\w+)\b/),
|
|
267
|
+
p("run", /\b(?:run|lint|linter|typecheck)\b/, 0.9),
|
|
268
|
+
p("docs", /\b(?:docs|documentation)\b/, 0.7),
|
|
269
|
+
p("recall", /\b(?:decided|decision|convention|agreed)\b/, 0.7),
|
|
270
|
+
p("plan", /\b(?:split|assign|rollout|roadmap|milestones?|coordinat\w*|orchestrat\w*)\b/, 0.9),
|
|
271
|
+
p("edit_small", /\b(?:rename\w*|typos?|tidy|reformat\w*|bump\w*|reword|indent\w*)\b/),
|
|
272
|
+
// Imperative and progressive forms only: "added" or "implementation" describe, not request.
|
|
273
|
+
p("implement", /\b(?:add|adding|write|writing|create|creating|introduce|build out)\b/, 0.8),
|
|
274
|
+
// Specific implement verbs name real work on their own (never complexity ≤ 1).
|
|
275
|
+
p(
|
|
276
|
+
"implement",
|
|
277
|
+
/\b(?:implement|implementing|optimi[sz]e|optimi[sz]ing|wire|wiring|convert|converting|extract|extracting|integrate|integrating|plumb|parallel[iy][sz]e|deduplicate)\b/
|
|
278
|
+
),
|
|
279
|
+
p("refactor", /\b(?:refactor\w*|migrat\w*|rewrit\w*|restructur\w*|consolidat\w*|overhaul\w*)\b/),
|
|
280
|
+
p("design", /\b(?:design\w*|redesign\w*|re-?architect\w*|architect\w*|subsystem)\b/),
|
|
281
|
+
p("review", /\b(?:review\w*|audit\w*)\b/),
|
|
282
|
+
p(
|
|
283
|
+
"debug",
|
|
284
|
+
/\b(?:why|debug\w*|fail\w*|slow|crash\w*|race|bug\w*|broken|leak\w*|hang\w*|flaky|error\w*|fix|fixing|deadlock\w*|regressed)\b/,
|
|
285
|
+
0.9
|
|
286
|
+
),
|
|
287
|
+
p("research", /\b(?:compar\w*|research\w*|providers?|evaluat\w*|benchmark\w*)\b/),
|
|
288
|
+
p("test", /\b(?:tests?|verify|regression|coverage)\b/, 0.8),
|
|
289
|
+
p("git", /\b(?:rebase|stash)\b/),
|
|
290
|
+
p(
|
|
291
|
+
"ui",
|
|
292
|
+
/\b(?:dashboard|css|component|view|layout|button|frontend|ui|charts?|badges?|tooltips?|modals?|sidebar|widgets?)\b/,
|
|
293
|
+
0.8
|
|
294
|
+
)
|
|
295
|
+
];
|
|
296
|
+
var LEAD_ONLY = [
|
|
297
|
+
p(
|
|
298
|
+
"implement",
|
|
299
|
+
/^(?:make|build|port|handle|store|support|expose|persist|track|enable|ensure|allow|teach|cache|upgrade|harden|speed)\b/,
|
|
300
|
+
0.8
|
|
301
|
+
),
|
|
302
|
+
// Generic change verbs: a small edit by default, but a change (so breadth counts in full).
|
|
303
|
+
p("edit_small", /^(?:update|change|modify|adjust|tweak)\b/, 0.3),
|
|
304
|
+
p("design", /^(?:propose|model)\b/, 0.8),
|
|
305
|
+
p("plan", /^(?:coordinate|orchestrate)\b/),
|
|
306
|
+
// amend and squash are code verbs too ("amend the docstring"); they mean git only with a commit
|
|
307
|
+
// as the object, which the commits-object phrase covers.
|
|
308
|
+
p("git", /^(?:commit|push|merge|rebase|stash)\b/)
|
|
309
|
+
// fetch/download/crawl/scrape are a docs lead only with a docs object: a phrase (FETCH_OBJECT)
|
|
310
|
+
];
|
|
311
|
+
var NON_LEAD = 0.7;
|
|
312
|
+
var SELECTOR = /\b(?<!\b(?:caus(?:e|es|ed|ing)|reasons?|sources?|origins?) (?:of |for )?)(?:the|every|each|all|any|only|of|no|some|which|those|these|its|their) (fail(?:ed|ing)) (?=[a-z])/g;
|
|
313
|
+
var CHAT = /^(?:hi|hello|hey|yo|thanks|thank you|thx|ty|ok|okay|yes|yep|no|nope|sure|cool|great|nice|perfect|got it|that works|sounds good|lgtm|good|done)\b[\s\w,.!']*$/;
|
|
314
|
+
var POLITE = /^\s*(?:(?:please|pls|can you|could you|would you|let'?s|i want to|i'?d like to|help me|now|ok(?:ay)?|so)[\s,]+)+/;
|
|
315
|
+
var TASK_TITLE = /^\s*(?:t\d+[a-z]?|task\s*#?\d+)\s*[:.)\]-]\s+/i;
|
|
316
|
+
var STRONG_SCOPE = /\b(?:whole|entire|codebase|code base|across|everywhere|throughout|globally|repo-wide|system-wide|project-wide|end-to-end|every (?:module|file|package|service)|(?:all|every|each) (?:the |of the |its |their )?call[- ]?sites?|all (?:the )?\d+|(?:\d{2,}|three|four|five|six|seven|eight|nine|ten|dozens? of|hundreds of) (?:[\w-]+ )?(?:files|modules|packages|services|hooks|handlers|endpoints|call sites|callers|components|tables|commands|tools|routes|repos))\b/g;
|
|
317
|
+
var WEAK_SCOPE = /\b(?:all|every|system|framework|platform|layer|subsystem|pipeline|architecture|monorepo|monolith\w*|call[- ]?sites?)\b/g;
|
|
318
|
+
var ENUM3 = /(?<![\w.-])[.-]*\b[\w.-]+(?: [\w.-]+)?, (?:the )?[\w.-]+(?: [\w.-]+)?,? (?:and|&) (?:the )?[\w.-]+/g;
|
|
319
|
+
var LIST3 = /(?<![\w.-])[.-]*\b[\w.-]+(?: [\w.-]+)?, (?:the |a |an )?[\w.-]+(?: [\w.-]+)?,? (?:and|&|or) (?:the |a |an )?[\w.-]+(?: [\w.-]+)?/g;
|
|
320
|
+
var LIST_SEP = /,? (?:and|&|or) |, /;
|
|
321
|
+
var UI_PART = new RegExp(
|
|
322
|
+
String.raw`\b(?:${UI_PLACE}|css|components?|buttons?|frontend|links?|inputs?|fields?|columns?|rows?|tables?|lists?|icons?|labels?|titles?|headings?|fonts?|colou?rs?|themes?|styles?|borders?|nav(?:bar)?s?|breadcrumbs?|toasts?|dropdowns?|checkbox(?:es)?|box(?:es)?|bars?|toggles?|sliders?|spinners?|banners?|popovers?|drawers?|avatars?|logos?|images?|grids?|filters?|graphs?|gauges?)\b`
|
|
323
|
+
);
|
|
324
|
+
var OTHER_LAYER = /\b(?:apis?|endpoints?|servers?|back[- ]?end|cli|command[- ]line|(?<!\b(?:custom|preact|react|use\w+) )hooks?|databases?|db|sqlite|sql|mcp|web ?sockets?|logins?|log-in|log ?into|log in (?:to|with|via|using|as)|sign[- ]?(?:in|on)|auth(?:n|z|enticat\w*|ori[sz]\w*)?)\b|(?<!\b(?:local|session) )\bstorage\b|(?:^|\s)--[a-z]/;
|
|
325
|
+
var RISK = /\b(?:secur\w*|vulnerab\w*|race conditions?|races?|migrat\w*|data loss|concurren\w*|production|prod|auth\w*|o?auth2?|oidc|pkce|sso|saml|jwt|csrf|xss|sandbox\w*|privilege\w*|multi-?tenan\w*|deadlock\w*|credentials?|secrets?|permissions?|delet\w* (?:\w+ ){0,2}(?:data|rows?|records?|tables?|users?|accounts?|database|db|buckets?|backups?|director(?:y|ies)|folders?)|drop (?:table|column)|backfill\w*|payments?|encrypt\w*|injection)\b/g;
|
|
326
|
+
var PERF = /\b(?:optimi[sz]\w*|perf\w*|latency|throughput|slow\w*|faster|speed\w*|bundle size|memory|cpu|p9\d|scal(?:e|es|ing)|takes|went up|grew|regress\w*)\b/;
|
|
327
|
+
var MEASURE = /\b\d[\d,.]*\s?(?:ms|µs|us|s|secs?|seconds?|minutes?|mins?|kb|mb|gb|tb|x|%|rps|qps)\b|\b(?:\d{4,}|\d[\d,.]*\s?[km]|\d{1,3}(?:,\d{3})+|a (?:million|billion|thousand))\s?[a-z]*\b/;
|
|
328
|
+
var INVESTIGATE = /\b(?:why|figure out|look into|investigat\w*|root[- ]?caus\w*|track down|get to the bottom of|diagnos\w*|dig into)\b/;
|
|
329
|
+
var EXPLAIN = /\b(?:explain|walk (?:me )?through)\b/;
|
|
330
|
+
var SYSTEM_SUBJECT = /\b(?:cach(?:e|es|ing)|invalidat\w*|builds?|pipelines?|jobs?|queues?|workers?|schedul\w*|cron|checkout|requests?|sessions?|startup|boot\w*|sync(?:ing|hroni[sz]\w*)?|replicat\w*|retr(?:y|ies)|lifecycle|concurren\w*|locking|index(?:ing|er)|ingest\w*|render(?:ing)?|deploy\w*|memory|latency|throughput|services?|server|database|transactions?|event loop|events|consistency|ordering|timeouts?|expiry|routing|middleware|webhooks?|connection pool|rate limit\w*|garbage collect\w*|token refresh)\b/;
|
|
331
|
+
var EMERGENT = /\b(?:twice|duplicat\w*|intermittent\w*|sometimes|randomly|occasionally|doubl(?:e|ed|es|ing)|tripl\w*|spik\w*|keeps? growing|over time|under load|feels? (?:slow|sluggish|laggy))\b/;
|
|
332
|
+
var THREAD = /\b(?:thread|plumb|propagat|pass|carry|forward)(?:s|es|ed|e|ing)? (?:[\w.'-]+ ){1,6}(?:all the way )?through\b/;
|
|
333
|
+
var IMPACT_OF_CHANGE = /\b(?:impact|effect|consequences?|blast radius) of (?:changing|renaming|removing|dropping|moving|splitting|deleting|replacing|reordering|making)\b|\bwhat (?:would|will) happen if (?:we |i )?(?:change|rename|remove|drop|move|split|delete|replace|make)\b/;
|
|
334
|
+
var FETCH = new RegExp(
|
|
335
|
+
String.raw`https?:\/\/\S+|\b${FETCH_VERB} (?:${FETCH_OBJECT})|\b(?:latest|current|newest) (?:version|release)s?\b|\brelease notes\b|\bchangelog\b`
|
|
336
|
+
);
|
|
337
|
+
var PARALLEL = /\b(?:in parallel|concurrently|at the same time|simultaneously|both at once|side by side|in one go|in a single (?:call|batch|run))\b|\b(?:and|&) (?:[\w.:-]+ ){1,4}(?:all |both )?at once\b/;
|
|
338
|
+
var REFS = /\b(?:what|who|which \w+) (?:else )?(?:calls?|uses?)\b|\b(?:code|callers?|places?) (?:that|which) (?:uses?|calls?|references?)\b|\b(?:callers|usages?|references) of\b/;
|
|
339
|
+
var CHANGE_IMPACT = /\bwhat (?:would|will|could|might) (?:break|change|fail)\b|\bwhat breaks\b|\b(?:blast radius|impact of|affected by)\b/;
|
|
340
|
+
var WRITE_INTENTS = ["implement", "edit_small", "refactor"];
|
|
341
|
+
var PATH_RE = /(?<![\w./-])[\w./-]+\.(?:ts|tsx|js|py|go|rs|md|json|ya?ml|sql|php)\b/g;
|
|
342
|
+
var SYMBOL_RE = /`[^`\n]+`|\b[a-z][a-z0-9]*[A-Z]\w*\b|\b[A-Z][a-z0-9]+[A-Z]\w*\b|\b[a-z0-9]+_[a-z0-9_]+\b/g;
|
|
343
|
+
var STACK_RE = /^[^\S\n\r]*at .+[^\s()]:\d+(?::\d+)?\)?[^\S\n\r]*$|Traceback \(most recent call last\)/m;
|
|
344
|
+
var PASTED_LINE = /^\s*(?:at .+:\d+|File ".+", line \d+|\[?\d{4}-\d\d-\d\d[T ]\d\d:\d\d|\[(?:error|warn(?:ing)?|info|debug|trace|fatal)\]|(?:ERROR|WARN(?:ING)?|INFO|DEBUG|TRACE|FATAL)\b|(?:error|warn(?:ing)?|fatal):|(?:FAIL|PASS|ERR!|×|✗|✓|✔)\s|(?:[A-Z]\w*)?(?:Error|Exception)\b|(?:Expected|Received|AssertionError)\b|npm (?:ERR|WARN)|\$ |> )/;
|
|
345
|
+
var FENCE = /```[\s\S]*?(?:```|$)/g;
|
|
346
|
+
var SCAN_CHARS = 4e3;
|
|
347
|
+
var count = (re, s) => s.match(re)?.length ?? 0;
|
|
348
|
+
var wordCount = (s) => {
|
|
349
|
+
const t = s.trim();
|
|
350
|
+
return t ? t.split(/\s+/).length : 0;
|
|
351
|
+
};
|
|
352
|
+
var SHARED_UI_NOUN = new RegExp(String.raw`\b${UI_NOUN}s$`);
|
|
353
|
+
var OWN_ARTICLE = /^(?:the|a|an) /;
|
|
354
|
+
var uiAmongAreas = (s) => {
|
|
355
|
+
for (const m of s.matchAll(LIST3)) {
|
|
356
|
+
const items = m[0].split(LIST_SEP);
|
|
357
|
+
const last = items[items.length - 1] ?? "";
|
|
358
|
+
if (SHARED_UI_NOUN.test(last) && !items.slice(1).some((x) => OWN_ARTICLE.test(x))) continue;
|
|
359
|
+
const ui = items.filter((x) => UI_PART.test(x)).length;
|
|
360
|
+
if (ui > 0 && ui < items.length) return true;
|
|
361
|
+
}
|
|
362
|
+
return false;
|
|
363
|
+
};
|
|
364
|
+
function extractIntents(text, libraries) {
|
|
365
|
+
let s = text.slice(0, SCAN_CHARS).toLowerCase().replace(TASK_TITLE, "").replace(POLITE, "");
|
|
366
|
+
const out = {};
|
|
367
|
+
const put = (i, v) => {
|
|
368
|
+
if (v > (out[i] ?? 0)) out[i] = Math.min(1, v);
|
|
369
|
+
};
|
|
370
|
+
const first = /^\s*\S+/.exec(s)?.[0].length ?? 0;
|
|
371
|
+
for (const l of PHRASES) {
|
|
372
|
+
const re = new RegExp(l.re.source, "g");
|
|
373
|
+
s = s.replace(re, (m, name) => {
|
|
374
|
+
if (l.pkg && typeof name === "string" && !isPackageName(name, libraries)) return m;
|
|
375
|
+
put(l.intent, l.w);
|
|
376
|
+
return " ".repeat(m.length);
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
const lead = s.slice(0, first).trim() ? /^\s*(\S+)/.exec(s) : null;
|
|
380
|
+
s = s.replace(SELECTOR, (m, w) => m.replace(w, " ".repeat(w.length)));
|
|
381
|
+
const leadEnd = lead ? lead[0].length : 0;
|
|
382
|
+
if (lead?.[1]) {
|
|
383
|
+
for (const l of LEAD_ONLY) if (l.re.test(lead[1])) put(l.intent, l.w);
|
|
384
|
+
}
|
|
385
|
+
for (const l of STEMS) {
|
|
386
|
+
const re = new RegExp(l.re.source, "g");
|
|
387
|
+
for (const m of s.matchAll(re)) put(l.intent, (m.index ?? 0) < leadEnd ? l.w : l.w * NON_LEAD);
|
|
388
|
+
}
|
|
389
|
+
return out;
|
|
390
|
+
}
|
|
391
|
+
function splitPasted(text) {
|
|
392
|
+
let pastedLines = 0;
|
|
393
|
+
const unfenced = text.replace(FENCE, (block) => {
|
|
394
|
+
for (const line of block.split("\n")) if (PASTED_LINE.test(line)) pastedLines++;
|
|
395
|
+
return "\n";
|
|
396
|
+
});
|
|
397
|
+
const kept = [];
|
|
398
|
+
for (const line of unfenced.split("\n")) {
|
|
399
|
+
if (PASTED_LINE.test(line)) pastedLines++;
|
|
400
|
+
else kept.push(line);
|
|
401
|
+
}
|
|
402
|
+
return { prose: kept.join("\n"), pastedLines };
|
|
403
|
+
}
|
|
404
|
+
function extractFeatures(text, hints) {
|
|
405
|
+
const trimmed = text.trim();
|
|
406
|
+
const words = wordCount(trimmed);
|
|
407
|
+
const stackTrace = STACK_RE.test(trimmed);
|
|
408
|
+
const { prose: prose2, pastedLines } = splitPasted(trimmed.slice(0, SCAN_CHARS));
|
|
409
|
+
const pasted2 = stackTrace || pastedLines >= 2;
|
|
410
|
+
const body = prose2.trim() ? prose2 : trimmed.slice(0, SCAN_CHARS);
|
|
411
|
+
const lower = body.toLowerCase();
|
|
412
|
+
const intents = extractIntents(body, hints?.libraries);
|
|
413
|
+
if (pasted2) intents.debug = Math.max(intents.debug ?? 0, 0.8);
|
|
414
|
+
const chat = words <= 8 && CHAT.test(lower) && Object.keys(intents).length === 0;
|
|
415
|
+
if (chat) intents.chat = 1;
|
|
416
|
+
const scope = Math.min(
|
|
417
|
+
1,
|
|
418
|
+
count(STRONG_SCOPE, lower) + 0.5 * (count(WEAK_SCOPE, lower) + count(ENUM3, lower))
|
|
419
|
+
);
|
|
420
|
+
const risk = Math.min(1, 0.5 * count(RISK, lower));
|
|
421
|
+
const perf = PERF.test(lower) && MEASURE.test(lower) ? 1 : 0;
|
|
422
|
+
const prosePaths = count(PATH_RE, body);
|
|
423
|
+
const named = prosePaths > 0 || count(SYMBOL_RE, body.replace(PATH_RE, " ")) > 0;
|
|
424
|
+
const systemic = SYSTEM_SUBJECT.test(lower) || EMERGENT.test(lower);
|
|
425
|
+
const investigate = systemic && (INVESTIGATE.test(lower) || !named && EXPLAIN.test(lower));
|
|
426
|
+
const crossCutting = THREAD.test(lower) || IMPACT_OF_CHANGE.test(lower);
|
|
427
|
+
const writes = WRITE_INTENTS.some((i) => (intents[i] ?? 0) > 0);
|
|
428
|
+
const fetch = FETCH.test(lower.replace(DOC_WRITE, " "));
|
|
429
|
+
const batch = (intents.run ?? 0) >= 0.9 && (intents.debug ?? 0) < 0.9 && !writes && (PARALLEL.test(lower) || count(ENUM3, lower) > 0);
|
|
430
|
+
const refs = REFS.test(lower) && !CHANGE_IMPACT.test(lower) && !crossCutting && !writes;
|
|
431
|
+
const otherLayer = OTHER_LAYER.test(lower) || uiAmongAreas(lower);
|
|
432
|
+
const classify = classifyPrompt(trimmed.slice(0, SCAN_CHARS)).find(
|
|
433
|
+
(d) => d.category !== "user_prompt" && d.category !== "intent"
|
|
434
|
+
)?.category;
|
|
435
|
+
const f = {
|
|
436
|
+
words,
|
|
437
|
+
chars: trimmed.length,
|
|
438
|
+
lines: trimmed ? trimmed.split("\n").length : 0,
|
|
439
|
+
paths: count(PATH_RE, trimmed),
|
|
440
|
+
symbols: count(SYMBOL_RE, trimmed.replace(PATH_RE, " ")),
|
|
441
|
+
codeBlock: trimmed.includes("```"),
|
|
442
|
+
stackTrace,
|
|
443
|
+
intents,
|
|
444
|
+
scope,
|
|
445
|
+
risk,
|
|
446
|
+
chat,
|
|
447
|
+
slash: trimmed.startsWith("/"),
|
|
448
|
+
proseWords: Math.min(words, wordCount(prose2)),
|
|
449
|
+
prosePaths,
|
|
450
|
+
pasted: pasted2,
|
|
451
|
+
taskTitle: TASK_TITLE.test(trimmed),
|
|
452
|
+
perf,
|
|
453
|
+
investigate,
|
|
454
|
+
crossCutting,
|
|
455
|
+
fetch,
|
|
456
|
+
batch,
|
|
457
|
+
refs,
|
|
458
|
+
otherLayer
|
|
459
|
+
};
|
|
460
|
+
if (classify) f.classify = classify;
|
|
461
|
+
if (hints && (hints.role || hints.files !== void 0 || hints.deps !== void 0)) {
|
|
462
|
+
f.task = { files: hints.files ?? 0, deps: hints.deps ?? 0 };
|
|
463
|
+
if (hints.role) f.task.role = hints.role;
|
|
464
|
+
}
|
|
465
|
+
return f;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// src/adjudicator/heuristic.ts
|
|
469
|
+
var DEFAULT_FEATURE_WEIGHTS = Object.freeze({
|
|
470
|
+
base: 0.6,
|
|
471
|
+
chat: -2,
|
|
472
|
+
edit_small: -0.6,
|
|
473
|
+
lookup: -0.3,
|
|
474
|
+
history: -0.3,
|
|
475
|
+
docs: -0.3,
|
|
476
|
+
git: -0.3,
|
|
477
|
+
impact: 0.6,
|
|
478
|
+
bigfile: 1,
|
|
479
|
+
recall: 0.6,
|
|
480
|
+
implement: 1,
|
|
481
|
+
debug: 1,
|
|
482
|
+
review: 1.2,
|
|
483
|
+
plan: 1.4,
|
|
484
|
+
refactor: 1.6,
|
|
485
|
+
design: 2.4,
|
|
486
|
+
research: 0.6,
|
|
487
|
+
test: 0.6,
|
|
488
|
+
run: 0,
|
|
489
|
+
ui: 0,
|
|
490
|
+
scope: 1.2,
|
|
491
|
+
risk: 0.8,
|
|
492
|
+
perf: 0.8,
|
|
493
|
+
paths: 0.3,
|
|
494
|
+
words40: 0.3,
|
|
495
|
+
words120: 0.6,
|
|
496
|
+
stackTrace: 0.4,
|
|
497
|
+
taskFiles: 0.4,
|
|
498
|
+
taskDeps: 0.2,
|
|
499
|
+
taskFloor: 2,
|
|
500
|
+
investigateFloor: 2,
|
|
501
|
+
crossCuttingFloor: 2,
|
|
502
|
+
toolProvider: 0,
|
|
503
|
+
asymmetry: 2
|
|
504
|
+
});
|
|
505
|
+
var DEFAULT_TIER_CENTERS = Object.freeze({
|
|
506
|
+
haiku: 0.6,
|
|
507
|
+
sonnet: 2,
|
|
508
|
+
opus: 3.4
|
|
509
|
+
});
|
|
510
|
+
var TAU = 0.5;
|
|
511
|
+
var AGENT_AT = 3.5;
|
|
512
|
+
var DEFAULT_TIERS = ["haiku", "sonnet", "opus"];
|
|
513
|
+
var r2 = (n) => Math.round(n * 100) / 100;
|
|
514
|
+
var clamp = (n, lo, hi) => Math.min(hi, Math.max(lo, n));
|
|
515
|
+
function complexityBucket(c) {
|
|
516
|
+
return clamp(Math.round(c), 0, 4);
|
|
517
|
+
}
|
|
518
|
+
var MAX_EFFORT_AT = 3.75;
|
|
519
|
+
function effortFor(c) {
|
|
520
|
+
if (c < 1.5) return "low";
|
|
521
|
+
if (c < 2.5) return "medium";
|
|
522
|
+
if (c < 3.5) return "high";
|
|
523
|
+
if (c < MAX_EFFORT_AT) return "xhigh";
|
|
524
|
+
return "max";
|
|
525
|
+
}
|
|
526
|
+
function topIntent(f) {
|
|
527
|
+
let best;
|
|
528
|
+
for (const [i, v] of Object.entries(f.intents))
|
|
529
|
+
if (v > 0 && (best === void 0 || v > (f.intents[best] ?? 0))) best = i;
|
|
530
|
+
return best;
|
|
531
|
+
}
|
|
532
|
+
function tierCenter(tier, index, centers) {
|
|
533
|
+
return centers[tier] ?? 0.6 + 1.4 * index;
|
|
534
|
+
}
|
|
535
|
+
function tableConfidence(entries) {
|
|
536
|
+
return 0.5 + ((entries[0]?.score ?? 0) - (entries[1]?.score ?? 0)) / 2;
|
|
537
|
+
}
|
|
538
|
+
function makeTable(dimension, options, raw, fallback) {
|
|
539
|
+
let total = 0;
|
|
540
|
+
for (const o of options) total += Math.max(0, raw.get(o)?.s ?? 0);
|
|
541
|
+
const fb = options.includes(fallback) ? fallback : options[0];
|
|
542
|
+
const entries = options.map((o) => {
|
|
543
|
+
const e = raw.get(o);
|
|
544
|
+
const score = total > 0 ? Math.max(0, e?.s ?? 0) / total : o === fb ? 1 : 0;
|
|
545
|
+
const reasons = e?.reasons ?? [];
|
|
546
|
+
if (total <= 0 && o === fb)
|
|
547
|
+
reasons.push({ feature: "fallback", weight: 1, note: `no signal; default ${fb}` });
|
|
548
|
+
return { option: o, score, reasons };
|
|
549
|
+
});
|
|
550
|
+
entries.sort((a, b) => b.score - a.score);
|
|
551
|
+
return {
|
|
552
|
+
dimension,
|
|
553
|
+
entries,
|
|
554
|
+
choice: entries[0]?.option ?? fallback,
|
|
555
|
+
confidence: tableConfidence(entries)
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
function add(raw, option, weight, feature, note) {
|
|
559
|
+
if (!weight) return;
|
|
560
|
+
const e = raw.get(option) ?? { s: 0, reasons: [] };
|
|
561
|
+
e.s += weight;
|
|
562
|
+
e.reasons.push({ feature, weight: r2(weight), note });
|
|
563
|
+
raw.set(option, e);
|
|
564
|
+
}
|
|
565
|
+
var CHANGES = [
|
|
566
|
+
"implement",
|
|
567
|
+
"edit_small",
|
|
568
|
+
"refactor",
|
|
569
|
+
"design",
|
|
570
|
+
"plan",
|
|
571
|
+
"review",
|
|
572
|
+
"debug",
|
|
573
|
+
"test",
|
|
574
|
+
"ui"
|
|
575
|
+
];
|
|
576
|
+
var READ_SCOPE = 0.5;
|
|
577
|
+
var WORK = [
|
|
578
|
+
"implement",
|
|
579
|
+
"edit_small",
|
|
580
|
+
"refactor",
|
|
581
|
+
"design",
|
|
582
|
+
"plan",
|
|
583
|
+
"review",
|
|
584
|
+
"debug",
|
|
585
|
+
"ui"
|
|
586
|
+
];
|
|
587
|
+
var BROAD = ["design", "plan", "refactor", "implement"];
|
|
588
|
+
function scoreComplexity(f, w) {
|
|
589
|
+
const reasons = [];
|
|
590
|
+
let c = 0;
|
|
591
|
+
const term = (feature, weight, note) => {
|
|
592
|
+
if (!weight) return;
|
|
593
|
+
c += weight;
|
|
594
|
+
reasons.push({ feature, weight: r2(weight), note });
|
|
595
|
+
};
|
|
596
|
+
const has = (i) => (f.intents[i] ?? 0) > 0;
|
|
597
|
+
const readOnly = !WORK.some(has) && (f.intents.test ?? 0) < 0.8;
|
|
598
|
+
term("base", w.base ?? 0, "base");
|
|
599
|
+
for (const [i, v] of Object.entries(f.intents)) {
|
|
600
|
+
if (!(v > 0)) continue;
|
|
601
|
+
if (readOnly && i === "test") continue;
|
|
602
|
+
term(`intent.${i}`, (w[i] ?? 0) * v, `${i} ${r2(v)} \xD7 ${w[i] ?? 0}`);
|
|
603
|
+
}
|
|
604
|
+
if (f.scope > 0) {
|
|
605
|
+
const changes = CHANGES.some((i) => (f.intents[i] ?? 0) > 0);
|
|
606
|
+
term(
|
|
607
|
+
"scope",
|
|
608
|
+
(w.scope ?? 0) * f.scope * (changes ? 1 : READ_SCOPE),
|
|
609
|
+
`scope ${r2(f.scope)}${changes ? "" : " (read only, halved)"}`
|
|
610
|
+
);
|
|
611
|
+
}
|
|
612
|
+
if (f.risk > 0) term("risk", (w.risk ?? 0) * f.risk, `risk ${r2(f.risk)}`);
|
|
613
|
+
if (f.perf) term("perf", (w.perf ?? 0) * f.perf, "performance work with a measured number");
|
|
614
|
+
const paths2 = f.prosePaths ?? f.paths;
|
|
615
|
+
if (paths2 > 0 && !readOnly)
|
|
616
|
+
term("paths", (w.paths ?? 0) * Math.log2(1 + paths2), `${paths2} path(s) named`);
|
|
617
|
+
const words = f.proseWords ?? f.words;
|
|
618
|
+
const report = has("debug") && !BROAD.some(has);
|
|
619
|
+
if (words > 120 && !report) term("words120", w.words120 ?? 0, `${words} words`);
|
|
620
|
+
else if (words > 40) term("words40", w.words40 ?? 0, `${words} words`);
|
|
621
|
+
if (f.stackTrace) term("stackTrace", w.stackTrace ?? 0, "stack trace");
|
|
622
|
+
if (f.task && f.task.files > 3)
|
|
623
|
+
term("taskFiles", w.taskFiles ?? 0, `task touches ${f.task.files} files`);
|
|
624
|
+
if (f.task && f.task.deps > 1) term("taskDeps", w.taskDeps ?? 0, `${f.task.deps} deps`);
|
|
625
|
+
const invFloor = w.investigateFloor ?? 0;
|
|
626
|
+
if (f.investigate && c < invFloor)
|
|
627
|
+
term("investigate", invFloor - c, `investigates system behaviour: floor ${invFloor}`);
|
|
628
|
+
const ccFloor = w.crossCuttingFloor ?? 0;
|
|
629
|
+
if (f.crossCutting && c < ccFloor)
|
|
630
|
+
term("crossCutting", ccFloor - c, `cross-cutting change: floor ${ccFloor}`);
|
|
631
|
+
const floor = w.taskFloor ?? 0;
|
|
632
|
+
if (f.taskTitle && c < floor) term("taskFloor", floor - c, `swarm task title: floor ${floor}`);
|
|
633
|
+
return { complexity: clamp(c, 0, 4), reasons };
|
|
634
|
+
}
|
|
635
|
+
function scoreTier(c, f, ladder, centers, asymmetry = 1) {
|
|
636
|
+
const a = Math.max(1, Number.isFinite(asymmetry) ? asymmetry : 1);
|
|
637
|
+
const sonnetAt = ladder.indexOf("sonnet");
|
|
638
|
+
const riskFrom = sonnetAt >= 0 ? sonnetAt : 1;
|
|
639
|
+
const logits = ladder.map((t, i) => {
|
|
640
|
+
const center = tierCenter(t, i, centers);
|
|
641
|
+
const under = c > center;
|
|
642
|
+
let s = -(under ? a : 1) * Math.abs(c - center);
|
|
643
|
+
const reasons = [
|
|
644
|
+
{
|
|
645
|
+
feature: "complexity",
|
|
646
|
+
weight: r2(s),
|
|
647
|
+
note: `c ${r2(c)} vs centre ${center}${under && a !== 1 ? ` (under-tier \xD7${r2(a)})` : ""}`
|
|
648
|
+
}
|
|
649
|
+
];
|
|
650
|
+
if (f.risk > 0 && i >= riskFrom) {
|
|
651
|
+
s += 0.4 * f.risk;
|
|
652
|
+
reasons.push({ feature: "risk", weight: r2(0.4 * f.risk), note: `risk ${r2(f.risk)}` });
|
|
653
|
+
}
|
|
654
|
+
if (f.chat && i === 0) {
|
|
655
|
+
s += 1;
|
|
656
|
+
reasons.push({ feature: "chat", weight: 1, note: "chat" });
|
|
657
|
+
}
|
|
658
|
+
return { t, s, reasons };
|
|
659
|
+
});
|
|
660
|
+
const max = Math.max(...logits.map((l) => l.s));
|
|
661
|
+
const raw = /* @__PURE__ */ new Map();
|
|
662
|
+
for (const l of logits) raw.set(l.t, { s: Math.exp((l.s - max) / TAU), reasons: l.reasons });
|
|
663
|
+
return makeTable("tier", ladder, raw, ladder[0] ?? "haiku");
|
|
664
|
+
}
|
|
665
|
+
var WRITES = ["implement", "edit_small", "refactor"];
|
|
666
|
+
function scoreTool(f, c, tools) {
|
|
667
|
+
const raw = /* @__PURE__ */ new Map();
|
|
668
|
+
const i = f.intents;
|
|
669
|
+
const rule = (intent, tool, w, extra = "") => {
|
|
670
|
+
const v = i[intent] ?? 0;
|
|
671
|
+
if (v > 0) add(raw, tool, w * v, `intent.${intent}`, `${intent} \u2192 ${tool}${extra}`);
|
|
672
|
+
};
|
|
673
|
+
const lookup = Math.max(0, (i.lookup ?? 0) - Math.max(i.docs ?? 0, i.history ?? 0));
|
|
674
|
+
if (lookup > 0) {
|
|
675
|
+
add(raw, "em_symbols", 0.6 * lookup, "intent.lookup", "lookup \u2192 em_symbols");
|
|
676
|
+
add(raw, "em_search", 0.4 * lookup, "intent.lookup", "lookup \u2192 em_search");
|
|
677
|
+
}
|
|
678
|
+
rule("impact", f.refs ? "em_symbols" : "em_graph_impact", 1, f.refs ? " (callers/usages)" : "");
|
|
679
|
+
rule("history", "em_history", 1);
|
|
680
|
+
const run = f.batch ? "em_batch_execute" : "em_execute";
|
|
681
|
+
rule("run", run, 1);
|
|
682
|
+
if (WRITES.some((w) => (i[w] ?? 0) > 0)) rule("test", "Edit", 1, " (write tests)");
|
|
683
|
+
else rule("test", run, 1);
|
|
684
|
+
rule("bigfile", "em_execute_file", 1);
|
|
685
|
+
rule("docs", f.fetch ? "em_fetch_and_index" : "em_docs", 0.6);
|
|
686
|
+
rule("docs", f.fetch ? "em_docs" : "em_fetch_and_index", 0.4);
|
|
687
|
+
rule("research", "em_fetch_and_index", 0.6);
|
|
688
|
+
rule("research", "em_search", 0.3);
|
|
689
|
+
rule("recall", "em_recall", 1);
|
|
690
|
+
rule("plan", "em_swarm", 1);
|
|
691
|
+
rule("edit_small", "Edit", 1);
|
|
692
|
+
rule("implement", "Edit", 1);
|
|
693
|
+
rule("git", "Bash", 1);
|
|
694
|
+
rule("debug", "em_execute", 0.6, " (reproduce)");
|
|
695
|
+
rule("debug", "em_search", 0.3);
|
|
696
|
+
rule("chat", "none", 1);
|
|
697
|
+
if (c >= AGENT_AT) {
|
|
698
|
+
rule("design", "Agent", 1.5, ` (c \u2265 ${AGENT_AT})`);
|
|
699
|
+
rule("refactor", "Agent", 1.5, ` (c \u2265 ${AGENT_AT})`);
|
|
700
|
+
rule("review", "Agent", 1, ` (c \u2265 ${AGENT_AT})`);
|
|
701
|
+
} else {
|
|
702
|
+
rule("design", "em_recall", 0.5, " (check past decisions)");
|
|
703
|
+
rule("refactor", "Edit", 0.8);
|
|
704
|
+
rule("review", "Agent", 0.6);
|
|
705
|
+
rule("review", "em_search", 0.3);
|
|
706
|
+
}
|
|
707
|
+
if (f.symbols > 0 || f.paths > 0) {
|
|
708
|
+
const v = Math.min(1, (f.symbols + f.paths) / 2);
|
|
709
|
+
add(raw, "em_symbols", 0.2 * v, "symbols", "symbols or paths named");
|
|
710
|
+
add(raw, "em_graph_impact", 0.1 * v, "symbols", "symbols or paths named");
|
|
711
|
+
}
|
|
712
|
+
const options = TOOL_IDS.filter((t) => tools.includes(t));
|
|
713
|
+
return makeTable("tool", options, raw, f.chat ? "none" : "em_search");
|
|
714
|
+
}
|
|
715
|
+
var ROLE_MAP = {
|
|
716
|
+
lookup: { explorer: 1 },
|
|
717
|
+
impact: { explorer: 1 },
|
|
718
|
+
bigfile: { explorer: 1 },
|
|
719
|
+
history: { explorer: 0.5, researcher: 0.5 },
|
|
720
|
+
docs: { researcher: 1 },
|
|
721
|
+
research: { researcher: 1 },
|
|
722
|
+
recall: { reviewer: 0.6, architect: 0.4 },
|
|
723
|
+
implement: { coder: 1 },
|
|
724
|
+
edit_small: { coder: 1 },
|
|
725
|
+
refactor: { coder: 1 },
|
|
726
|
+
debug: { coder: 1 },
|
|
727
|
+
git: { coder: 1 },
|
|
728
|
+
test: { tester: 1.2 },
|
|
729
|
+
run: { tester: 1 },
|
|
730
|
+
review: { reviewer: 1.2 },
|
|
731
|
+
design: { architect: 1.2 },
|
|
732
|
+
plan: { architect: 1.2 },
|
|
733
|
+
ui: { ui: 0.8 },
|
|
734
|
+
chat: { none: 1 }
|
|
735
|
+
};
|
|
736
|
+
var profiles = /* @__PURE__ */ new Map();
|
|
737
|
+
function profile(name, description) {
|
|
738
|
+
const k = `${name}
|
|
739
|
+
${description}`;
|
|
740
|
+
let p2 = profiles.get(k);
|
|
741
|
+
if (!p2) {
|
|
742
|
+
p2 = extractIntents(`${name} ${description}`);
|
|
743
|
+
if (profiles.size > 256) profiles.clear();
|
|
744
|
+
profiles.set(k, p2);
|
|
745
|
+
}
|
|
746
|
+
return p2;
|
|
747
|
+
}
|
|
748
|
+
function scoreRole(f, catalogue, c = 0) {
|
|
749
|
+
const raw = /* @__PURE__ */ new Map();
|
|
750
|
+
const options = catalogue?.length ? catalogue.map((r) => r.name) : [...DEFAULT_ROLES];
|
|
751
|
+
const known = new Set(options);
|
|
752
|
+
const bounded2 = !(f.intents.refactor ?? 0) && f.scope < 1;
|
|
753
|
+
const crossLayer = bounded2 && (f.risk > 0 || f.otherLayer === true);
|
|
754
|
+
const uiWork = known.has("ui") && (f.intents.ui ?? 0) > 0 && bounded2 && !crossLayer;
|
|
755
|
+
for (const [intent, v] of Object.entries(f.intents)) {
|
|
756
|
+
if (!(v > 0)) continue;
|
|
757
|
+
const edit = intent === "implement" || intent === "edit_small";
|
|
758
|
+
for (const [r, w] of Object.entries(ROLE_MAP[intent] ?? {})) {
|
|
759
|
+
const role = uiWork && edit && r === "coder" ? "ui" : crossLayer && r === "ui" && known.has("coder") ? "coder" : r;
|
|
760
|
+
if (known.has(role)) add(raw, role, w * v, `intent.${intent}`, `${intent} \u2192 ${role}`);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
for (const r of catalogue ?? []) {
|
|
764
|
+
if (!r.description) continue;
|
|
765
|
+
const prof = profile(r.name, r.description);
|
|
766
|
+
let overlap = 0;
|
|
767
|
+
for (const [intent, v] of Object.entries(f.intents))
|
|
768
|
+
overlap += v * (prof[intent] ?? 0);
|
|
769
|
+
if (overlap > 0) add(raw, r.name, 0.5 * overlap, "description", "matches the role description");
|
|
770
|
+
}
|
|
771
|
+
const refactor = f.intents.refactor ?? 0;
|
|
772
|
+
if (c >= AGENT_AT && refactor > 0 && known.has("architect"))
|
|
773
|
+
add(
|
|
774
|
+
raw,
|
|
775
|
+
"architect",
|
|
776
|
+
1.2 * refactor,
|
|
777
|
+
"intent.refactor",
|
|
778
|
+
`large refactor (c \u2265 ${AGENT_AT}) \u2192 architect`
|
|
779
|
+
);
|
|
780
|
+
if (f.task?.role && known.has(f.task.role))
|
|
781
|
+
add(raw, f.task.role, 1, "task.role", "role assigned by the plan");
|
|
782
|
+
const fallback = f.chat || Object.keys(f.intents).length === 0 ? "none" : "explorer";
|
|
783
|
+
return makeTable("role", options, raw, known.has(fallback) ? fallback : options[0] ?? "none");
|
|
784
|
+
}
|
|
785
|
+
function scoreHeuristic(f, weights = {}, catalogue = {}) {
|
|
786
|
+
const w = { ...DEFAULT_FEATURE_WEIGHTS, ...weights.features ?? {} };
|
|
787
|
+
const centers = { ...DEFAULT_TIER_CENTERS, ...weights.tierCenters ?? {} };
|
|
788
|
+
const ladder = (catalogue.tiers?.length ? catalogue.tiers : DEFAULT_TIERS).filter(
|
|
789
|
+
(t) => TIERS.includes(t)
|
|
790
|
+
);
|
|
791
|
+
const { complexity, reasons } = scoreComplexity(f, w);
|
|
792
|
+
return {
|
|
793
|
+
complexity,
|
|
794
|
+
complexityReasons: reasons,
|
|
795
|
+
tool: scoreTool(f, complexity, catalogue.tools ?? TOOL_IDS),
|
|
796
|
+
tier: scoreTier(complexity, f, ladder, centers, w.asymmetry),
|
|
797
|
+
role: scoreRole(f, catalogue.roles, complexity),
|
|
798
|
+
effort: effortFor(complexity)
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
// src/adjudicator/blend.ts
|
|
803
|
+
var BLEND_DEFAULTS = Object.freeze({
|
|
804
|
+
providerMax: 0.7,
|
|
805
|
+
embeddingMax: 0.4,
|
|
806
|
+
selfReportScale: 0.6,
|
|
807
|
+
lowConfidence: 0.55
|
|
808
|
+
});
|
|
809
|
+
var NOUL_BOOST = 0.3;
|
|
810
|
+
var NOUL_AT = 0.7;
|
|
811
|
+
var r22 = (n) => Math.round(n * 100) / 100;
|
|
812
|
+
var clamp01 = (n) => Math.min(1, Math.max(0, n));
|
|
813
|
+
function providerAlpha(provider, answer, weights = {}) {
|
|
814
|
+
if (!answer || provider === "heuristic") return {};
|
|
815
|
+
const w = { ...BLEND_DEFAULTS, ...weights };
|
|
816
|
+
const embedding = provider === "embedding";
|
|
817
|
+
const cap2 = clamp01(embedding ? w.embeddingMax : w.providerMax);
|
|
818
|
+
const scale = clamp01(w.selfReportScale);
|
|
819
|
+
const conf = (d) => clamp01(d.calibrated ? d.confidence : d.confidence * scale);
|
|
820
|
+
const out = {};
|
|
821
|
+
if (answer.complexity) {
|
|
822
|
+
const calibrated = answer.tier?.calibrated ?? provider === "typesafe";
|
|
823
|
+
const c = clamp01(answer.complexity.confidence * (calibrated ? 1 : scale));
|
|
824
|
+
out.complexity = Math.min(cap2, c);
|
|
825
|
+
}
|
|
826
|
+
if (answer.tier) out.tier = Math.min(cap2, conf(answer.tier));
|
|
827
|
+
if (!embedding) {
|
|
828
|
+
if (answer.role) out.role = Math.min(cap2, conf(answer.role));
|
|
829
|
+
const toolProvider = clamp01(w.features?.toolProvider ?? 0);
|
|
830
|
+
if (answer.tool && toolProvider > 0) out.tool = Math.min(cap2, toolProvider, conf(answer.tool));
|
|
831
|
+
}
|
|
832
|
+
return out;
|
|
833
|
+
}
|
|
834
|
+
function mixTable(h, dist, alpha, provider, adds, bias) {
|
|
835
|
+
const options = h.entries.map((e) => e.option);
|
|
836
|
+
let pSum = 0;
|
|
837
|
+
if (dist && alpha > 0) for (const o of options) pSum += Math.max(0, dist.probs[o] ?? 0);
|
|
838
|
+
const a = pSum > 0 ? alpha : 0;
|
|
839
|
+
const rows = h.entries.map((e) => {
|
|
840
|
+
const reasons = [...e.reasons];
|
|
841
|
+
const p2 = a > 0 && dist ? Math.max(0, dist.probs[e.option] ?? 0) / pSum : 0;
|
|
842
|
+
let s = (1 - a) * e.score + a * p2;
|
|
843
|
+
if (a > 0 && p2 > 0)
|
|
844
|
+
reasons.push({
|
|
845
|
+
feature: "provider",
|
|
846
|
+
weight: r22(a * p2),
|
|
847
|
+
note: `${provider} p ${r22(p2)} \xD7 \u03B1 ${r22(a)}`
|
|
848
|
+
});
|
|
849
|
+
for (const add2 of adds)
|
|
850
|
+
if (add2.option === e.option) {
|
|
851
|
+
s += add2.weight;
|
|
852
|
+
reasons.push(add2.reason);
|
|
853
|
+
}
|
|
854
|
+
const b = bias[`${h.dimension}:${e.option}`] ?? 0;
|
|
855
|
+
if (b) {
|
|
856
|
+
s += b;
|
|
857
|
+
reasons.push({ feature: "bias", weight: r22(b), note: "learned from outcomes" });
|
|
858
|
+
}
|
|
859
|
+
return { option: e.option, s: Math.max(0, s), reasons };
|
|
860
|
+
});
|
|
861
|
+
const total = rows.reduce((n, r) => n + r.s, 0);
|
|
862
|
+
if (!(total > 0)) return { table: h, alpha: a };
|
|
863
|
+
const entries = rows.map((r) => ({ option: r.option, score: r.s / total, reasons: r.reasons })).sort((x, y) => y.score - x.score);
|
|
864
|
+
return {
|
|
865
|
+
table: {
|
|
866
|
+
dimension: h.dimension,
|
|
867
|
+
entries,
|
|
868
|
+
choice: entries[0]?.option ?? h.choice,
|
|
869
|
+
confidence: tableConfidence(entries)
|
|
870
|
+
},
|
|
871
|
+
alpha: a
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
function ladderOf(table, tiers) {
|
|
875
|
+
const present = new Set(table.entries.map((e) => e.option));
|
|
876
|
+
const base = tiers?.length ? tiers : TIERS;
|
|
877
|
+
const ladder = base.filter((t) => present.has(t));
|
|
878
|
+
return ladder.length ? ladder : [...DEFAULT_TIERS];
|
|
879
|
+
}
|
|
880
|
+
function blend(h, answer, bias = {}, weights = {}, opts = { key: "", provider: "heuristic" }) {
|
|
881
|
+
const w = { ...BLEND_DEFAULTS, ...weights };
|
|
882
|
+
const alpha = providerAlpha(opts.provider, answer, weights);
|
|
883
|
+
const tierLadder = ladderOf(h.tier, opts.tiers);
|
|
884
|
+
const sonnetAt = tierLadder.indexOf("sonnet");
|
|
885
|
+
const riskFrom = sonnetAt >= 0 ? sonnetAt : 1;
|
|
886
|
+
let complexity = h.complexity;
|
|
887
|
+
const complexityReasons = [...h.complexityReasons];
|
|
888
|
+
const ac = alpha.complexity ?? 0;
|
|
889
|
+
if (answer?.complexity && ac > 0) {
|
|
890
|
+
const cp = Math.min(4, Math.max(0, answer.complexity.value));
|
|
891
|
+
complexity = (1 - ac) * h.complexity + ac * cp;
|
|
892
|
+
complexityReasons.push({
|
|
893
|
+
feature: "provider",
|
|
894
|
+
weight: r22(complexity - h.complexity),
|
|
895
|
+
note: `${opts.provider} says ${r22(cp)} (\u03B1 ${r22(ac)})`
|
|
896
|
+
});
|
|
897
|
+
}
|
|
898
|
+
const tierAdds = [];
|
|
899
|
+
const toolAdds = [];
|
|
900
|
+
const roleAdds = [];
|
|
901
|
+
if ((answer?.risky ?? 0) > NOUL_AT)
|
|
902
|
+
for (const t of tierLadder.slice(riskFrom))
|
|
903
|
+
tierAdds.push({
|
|
904
|
+
option: t,
|
|
905
|
+
weight: NOUL_BOOST,
|
|
906
|
+
reason: {
|
|
907
|
+
feature: "risky",
|
|
908
|
+
weight: NOUL_BOOST,
|
|
909
|
+
note: `${opts.provider}: risky ${r22(answer?.risky ?? 0)}`
|
|
910
|
+
}
|
|
911
|
+
});
|
|
912
|
+
if ((answer?.needsPlan ?? 0) > NOUL_AT) {
|
|
913
|
+
const reason = (o) => ({
|
|
914
|
+
option: o,
|
|
915
|
+
weight: NOUL_BOOST,
|
|
916
|
+
reason: {
|
|
917
|
+
feature: "needsPlan",
|
|
918
|
+
weight: NOUL_BOOST,
|
|
919
|
+
note: `${opts.provider}: needs a plan ${r22(answer?.needsPlan ?? 0)}`
|
|
920
|
+
}
|
|
921
|
+
});
|
|
922
|
+
toolAdds.push(reason("em_swarm"));
|
|
923
|
+
roleAdds.push(reason("architect"));
|
|
924
|
+
}
|
|
925
|
+
const tier = mixTable(h.tier, answer?.tier, alpha.tier ?? 0, opts.provider, tierAdds, bias);
|
|
926
|
+
const role = mixTable(h.role, answer?.role, alpha.role ?? 0, opts.provider, roleAdds, bias);
|
|
927
|
+
const tool = mixTable(h.tool, answer?.tool, alpha.tool ?? 0, opts.provider, toolAdds, bias);
|
|
928
|
+
let tierTable = tier.table;
|
|
929
|
+
if (tierTable.confidence < w.lowConfidence) {
|
|
930
|
+
const at = tierLadder.indexOf(tierTable.choice);
|
|
931
|
+
const up = at >= 0 && at < tierLadder.length - 1 ? tierLadder[at + 1] : void 0;
|
|
932
|
+
if (up) {
|
|
933
|
+
tierTable = {
|
|
934
|
+
...tierTable,
|
|
935
|
+
choice: up,
|
|
936
|
+
entries: tierTable.entries.map(
|
|
937
|
+
(e) => e.option === up ? {
|
|
938
|
+
...e,
|
|
939
|
+
reasons: [
|
|
940
|
+
...e.reasons,
|
|
941
|
+
{
|
|
942
|
+
feature: "lowConfidence",
|
|
943
|
+
weight: r22(w.lowConfidence - tierTable.confidence),
|
|
944
|
+
note: "low confidence \u2192 rounded up"
|
|
945
|
+
}
|
|
946
|
+
]
|
|
947
|
+
} : e
|
|
948
|
+
)
|
|
949
|
+
};
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
const effective = {};
|
|
953
|
+
if (ac > 0 && answer?.complexity) effective.complexity = r22(ac);
|
|
954
|
+
if (tier.alpha > 0) effective.tier = r22(tier.alpha);
|
|
955
|
+
if (role.alpha > 0) effective.role = r22(role.alpha);
|
|
956
|
+
if (tool.alpha > 0) effective.tool = r22(tool.alpha);
|
|
957
|
+
const v = {
|
|
958
|
+
key: opts.key,
|
|
959
|
+
complexity,
|
|
960
|
+
complexityReasons,
|
|
961
|
+
tool: tool.table,
|
|
962
|
+
tier: tierTable,
|
|
963
|
+
role: role.table,
|
|
964
|
+
effort: effortFor(complexity),
|
|
965
|
+
source: opts.source ?? (answer && opts.provider !== "heuristic" ? "blend" : "heuristic"),
|
|
966
|
+
provider: opts.provider,
|
|
967
|
+
providerStatus: opts.providerStatus ?? (answer ? "ok" : "n/a"),
|
|
968
|
+
alpha: effective,
|
|
969
|
+
bias,
|
|
970
|
+
latencyMs: opts.latencyMs ?? 0,
|
|
971
|
+
version: SCORER_VERSION
|
|
972
|
+
};
|
|
973
|
+
if (opts.providerLatencyMs !== void 0) v.providerLatencyMs = opts.providerLatencyMs;
|
|
974
|
+
if (opts.usage) v.usage = opts.usage;
|
|
975
|
+
return v;
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
// src/adjudicator/breaker.ts
|
|
979
|
+
init_esm_shims();
|
|
980
|
+
import { createHash } from "crypto";
|
|
981
|
+
var SLOW_MIN_SAMPLES = 20;
|
|
982
|
+
var AUTH_LOCKOUT_MS = 36e5;
|
|
983
|
+
function utcDay(now) {
|
|
984
|
+
return new Date(now).toISOString().slice(0, 10);
|
|
985
|
+
}
|
|
986
|
+
function p95(values) {
|
|
987
|
+
if (!values.length) return void 0;
|
|
988
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
989
|
+
return sorted[Math.min(sorted.length - 1, Math.ceil(0.95 * sorted.length) - 1)];
|
|
990
|
+
}
|
|
991
|
+
function spendToday(s, now) {
|
|
992
|
+
if (!s || s.spendDay !== utcDay(now)) return 0;
|
|
993
|
+
return s.spendUsd;
|
|
994
|
+
}
|
|
995
|
+
function callsToday(s, now) {
|
|
996
|
+
if (!s || s.spendDay !== utcDay(now)) return 0;
|
|
997
|
+
return s.calls ?? 0;
|
|
998
|
+
}
|
|
999
|
+
function keyFingerprint(key) {
|
|
1000
|
+
return createHash("sha256").update(key).digest("hex").slice(0, 12);
|
|
1001
|
+
}
|
|
1002
|
+
function isAuthFailure(kind, status) {
|
|
1003
|
+
return kind === "http_error" && (status === 401 || status === 403);
|
|
1004
|
+
}
|
|
1005
|
+
function authLockout(s, now, currentFingerprint) {
|
|
1006
|
+
if (s?.state !== "auth_failed") return void 0;
|
|
1007
|
+
const keyChanged = currentFingerprint !== void 0 && (currentFingerprint ?? null) !== s.authFingerprint;
|
|
1008
|
+
const expired = s.retryAt === null || now >= s.retryAt;
|
|
1009
|
+
return {
|
|
1010
|
+
locked: !keyChanged && !expired,
|
|
1011
|
+
until: s.retryAt,
|
|
1012
|
+
fingerprint: s.authFingerprint,
|
|
1013
|
+
keyChanged,
|
|
1014
|
+
errorClass: s.lastErrorClass
|
|
1015
|
+
};
|
|
1016
|
+
}
|
|
1017
|
+
var unrecordedByStore = /* @__PURE__ */ new WeakMap();
|
|
1018
|
+
function unrecorded(store, provider, now) {
|
|
1019
|
+
const day = utcDay(now);
|
|
1020
|
+
const u = unrecordedByStore.get(store)?.get(provider);
|
|
1021
|
+
return u && u.day === day ? { ...u } : { day, calls: 0, spendUsd: 0 };
|
|
1022
|
+
}
|
|
1023
|
+
function carry(store, provider, now, calls, spendUsd) {
|
|
1024
|
+
if (!calls && !spendUsd) return;
|
|
1025
|
+
const u = unrecorded(store, provider, now);
|
|
1026
|
+
let m = unrecordedByStore.get(store);
|
|
1027
|
+
if (!m) {
|
|
1028
|
+
m = /* @__PURE__ */ new Map();
|
|
1029
|
+
unrecordedByStore.set(store, m);
|
|
1030
|
+
}
|
|
1031
|
+
m.set(provider, { day: u.day, calls: u.calls + calls, spendUsd: u.spendUsd + spendUsd });
|
|
1032
|
+
}
|
|
1033
|
+
function settle(store, provider) {
|
|
1034
|
+
unrecordedByStore.get(store)?.delete(provider);
|
|
1035
|
+
}
|
|
1036
|
+
function withUnrecorded(s, u, now) {
|
|
1037
|
+
if (!u.calls && !u.spendUsd) return s;
|
|
1038
|
+
const base = s ?? {
|
|
1039
|
+
provider: "",
|
|
1040
|
+
state: "closed",
|
|
1041
|
+
failures: 0,
|
|
1042
|
+
retryAt: null,
|
|
1043
|
+
lastOkAt: null,
|
|
1044
|
+
lastErrorAt: null,
|
|
1045
|
+
lastErrorClass: null,
|
|
1046
|
+
latencies: [],
|
|
1047
|
+
spendDay: null,
|
|
1048
|
+
spendUsd: 0,
|
|
1049
|
+
calls: 0,
|
|
1050
|
+
authFingerprint: null
|
|
1051
|
+
};
|
|
1052
|
+
return {
|
|
1053
|
+
...base,
|
|
1054
|
+
spendDay: u.day,
|
|
1055
|
+
spendUsd: spendToday(s, now) + u.spendUsd,
|
|
1056
|
+
calls: callsToday(s, now) + u.calls
|
|
1057
|
+
};
|
|
1058
|
+
}
|
|
1059
|
+
function gate(store, provider, cfg, o) {
|
|
1060
|
+
const u = unrecorded(store, provider, o.now);
|
|
1061
|
+
const s = store.providerState(provider);
|
|
1062
|
+
const read = planGate(withUnrecorded(s, u, o.now), cfg, o);
|
|
1063
|
+
if (read !== "write") return read.allow ? { ...read, state: s } : read;
|
|
1064
|
+
return atomically(store, () => gateTx(store, provider, cfg, o, u), o.lockWaitMs);
|
|
1065
|
+
}
|
|
1066
|
+
function planGate(s, cfg, o) {
|
|
1067
|
+
const over = o.remote && overBudget(s, cfg, o.now);
|
|
1068
|
+
const future = s?.retryAt !== null && s?.retryAt !== void 0 && o.now < s.retryAt;
|
|
1069
|
+
if (s?.state === "auth_failed") {
|
|
1070
|
+
if (authLockout(s, o.now, o.keyFingerprint)?.locked)
|
|
1071
|
+
return { allow: false, status: "auth_failed" };
|
|
1072
|
+
} else if (s?.state === "open" || s?.state === "half_open") {
|
|
1073
|
+
if (future) return { allow: false, status: "breaker_open" };
|
|
1074
|
+
} else if (s?.state === "slow") {
|
|
1075
|
+
if (future) return { allow: false, status: "slow_guard" };
|
|
1076
|
+
} else {
|
|
1077
|
+
return over ? { allow: false, status: "over_budget" } : { allow: true, halfOpen: false, state: s };
|
|
1078
|
+
}
|
|
1079
|
+
return over ? { allow: false, status: "over_budget" } : "write";
|
|
1080
|
+
}
|
|
1081
|
+
function gateTx(store, provider, cfg, o, u) {
|
|
1082
|
+
let s = store.providerState(provider);
|
|
1083
|
+
const cooldownMs = cfg.breaker.cooldownSec * 1e3;
|
|
1084
|
+
let halfOpen = false;
|
|
1085
|
+
if (s?.state === "auth_failed") {
|
|
1086
|
+
const lock = authLockout(s, o.now, o.keyFingerprint);
|
|
1087
|
+
if (lock?.locked) return { allow: false, status: "auth_failed" };
|
|
1088
|
+
s = store.saveProviderState(provider, {
|
|
1089
|
+
state: "closed",
|
|
1090
|
+
retryAt: null,
|
|
1091
|
+
authFingerprint: null
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
1094
|
+
if (s?.state === "open" || s?.state === "half_open") {
|
|
1095
|
+
if (s.retryAt !== null && o.now < s.retryAt) return { allow: false, status: "breaker_open" };
|
|
1096
|
+
s = store.saveProviderState(provider, { state: "half_open", retryAt: o.now + cooldownMs });
|
|
1097
|
+
halfOpen = true;
|
|
1098
|
+
} else if (s?.state === "slow") {
|
|
1099
|
+
if (s.retryAt !== null && o.now < s.retryAt) return { allow: false, status: "slow_guard" };
|
|
1100
|
+
s = store.saveProviderState(provider, {
|
|
1101
|
+
state: "closed",
|
|
1102
|
+
retryAt: null,
|
|
1103
|
+
latencies: [],
|
|
1104
|
+
latencyBudgets: []
|
|
1105
|
+
});
|
|
1106
|
+
}
|
|
1107
|
+
if (o.remote && overBudget(withUnrecorded(s, u, o.now), cfg, o.now)) {
|
|
1108
|
+
if (halfOpen) store.saveProviderState(provider, { state: "open", retryAt: o.now });
|
|
1109
|
+
return { allow: false, status: "over_budget" };
|
|
1110
|
+
}
|
|
1111
|
+
return { allow: true, halfOpen, state: s };
|
|
1112
|
+
}
|
|
1113
|
+
function overBudget(s, cfg, now) {
|
|
1114
|
+
if (spendToday(s, now) >= cfg.budget.dailyUsd) return true;
|
|
1115
|
+
const cap2 = cfg.budget.dailyCalls;
|
|
1116
|
+
return typeof cap2 === "number" && cap2 > 0 && callsToday(s, now) >= cap2;
|
|
1117
|
+
}
|
|
1118
|
+
function recordSuccess(store, provider, cfg, o) {
|
|
1119
|
+
const u = unrecorded(store, provider, o.now);
|
|
1120
|
+
return recorded(
|
|
1121
|
+
store,
|
|
1122
|
+
provider,
|
|
1123
|
+
o.now,
|
|
1124
|
+
1,
|
|
1125
|
+
o.costUsd ?? 0,
|
|
1126
|
+
() => atomically(
|
|
1127
|
+
store,
|
|
1128
|
+
() => {
|
|
1129
|
+
const s = store.providerState(provider);
|
|
1130
|
+
const day = utcDay(o.now);
|
|
1131
|
+
const spent = spendToday(s, o.now) + u.spendUsd + (o.costUsd ?? 0);
|
|
1132
|
+
const ring = o.sample === false ? ringOf(s) : addSample(s, o.latencyMs, o.budgetMs);
|
|
1133
|
+
const slow = isSlow(ring);
|
|
1134
|
+
return store.saveProviderState(provider, {
|
|
1135
|
+
state: slow ? "slow" : "closed",
|
|
1136
|
+
failures: 0,
|
|
1137
|
+
retryAt: slow ? o.now + cfg.breaker.cooldownSec * 1e3 : null,
|
|
1138
|
+
lastOkAt: o.now,
|
|
1139
|
+
...ring,
|
|
1140
|
+
spendDay: day,
|
|
1141
|
+
spendUsd: spent,
|
|
1142
|
+
calls: callsToday(s, o.now) + u.calls + 1,
|
|
1143
|
+
authFingerprint: null
|
|
1144
|
+
});
|
|
1145
|
+
},
|
|
1146
|
+
o.lockWaitMs
|
|
1147
|
+
)
|
|
1148
|
+
);
|
|
1149
|
+
}
|
|
1150
|
+
function recorded(store, provider, now, calls, spendUsd, fn) {
|
|
1151
|
+
let out;
|
|
1152
|
+
try {
|
|
1153
|
+
out = fn();
|
|
1154
|
+
} catch (err) {
|
|
1155
|
+
carry(store, provider, now, calls, spendUsd);
|
|
1156
|
+
throw err;
|
|
1157
|
+
}
|
|
1158
|
+
settle(store, provider);
|
|
1159
|
+
return out;
|
|
1160
|
+
}
|
|
1161
|
+
function countsAsFailure(kind, status) {
|
|
1162
|
+
if (kind === "timeout" || kind === "rate_limited") return true;
|
|
1163
|
+
if (kind === "http_error") return status === void 0 || status >= 500;
|
|
1164
|
+
return false;
|
|
1165
|
+
}
|
|
1166
|
+
function recordFailure(store, provider, cfg, o) {
|
|
1167
|
+
const u = unrecorded(store, provider, o.now);
|
|
1168
|
+
const calls = o.kind === "no_key" ? 0 : 1;
|
|
1169
|
+
return recorded(
|
|
1170
|
+
store,
|
|
1171
|
+
provider,
|
|
1172
|
+
o.now,
|
|
1173
|
+
calls,
|
|
1174
|
+
0,
|
|
1175
|
+
() => atomically(store, () => recordFailureTx(store, provider, cfg, o, u), o.lockWaitMs)
|
|
1176
|
+
);
|
|
1177
|
+
}
|
|
1178
|
+
function recordFailureTx(store, provider, cfg, o, u) {
|
|
1179
|
+
const s = store.providerState(provider);
|
|
1180
|
+
const cooldownMs = cfg.breaker.cooldownSec * 1e3;
|
|
1181
|
+
const errorClass = o.status !== void 0 ? `${o.kind}:${o.status}` : o.kind;
|
|
1182
|
+
const day = { spendDay: utcDay(o.now), spendUsd: spendToday(s, o.now) + u.spendUsd };
|
|
1183
|
+
const calls = callsToday(s, o.now) + u.calls + (o.kind === "no_key" ? 0 : 1);
|
|
1184
|
+
if (isAuthFailure(o.kind, o.status)) {
|
|
1185
|
+
return store.saveProviderState(provider, {
|
|
1186
|
+
state: "auth_failed",
|
|
1187
|
+
retryAt: o.now + AUTH_LOCKOUT_MS,
|
|
1188
|
+
authFingerprint: o.keyFingerprint ?? null,
|
|
1189
|
+
lastErrorAt: o.now,
|
|
1190
|
+
lastErrorClass: errorClass,
|
|
1191
|
+
...day,
|
|
1192
|
+
calls
|
|
1193
|
+
});
|
|
1194
|
+
}
|
|
1195
|
+
const counts = countsAsFailure(o.kind, o.status);
|
|
1196
|
+
const failures = counts ? (s?.failures ?? 0) + 1 : s?.failures ?? 0;
|
|
1197
|
+
const ring = o.kind === "timeout" && o.sample !== false ? addSample(s, timeoutLatency(o.latencyMs, o.budgetMs), o.budgetMs) : ringOf(s);
|
|
1198
|
+
let state;
|
|
1199
|
+
let retryAt;
|
|
1200
|
+
if (counts && (o.halfOpen || failures >= cfg.breaker.failures)) {
|
|
1201
|
+
state = "open";
|
|
1202
|
+
retryAt = o.now + cooldownMs;
|
|
1203
|
+
} else if (o.halfOpen) {
|
|
1204
|
+
state = "closed";
|
|
1205
|
+
retryAt = null;
|
|
1206
|
+
} else if (isSlow(ring)) {
|
|
1207
|
+
state = "slow";
|
|
1208
|
+
retryAt = o.now + cooldownMs;
|
|
1209
|
+
} else {
|
|
1210
|
+
state = s?.state === "slow" ? "closed" : s?.state ?? "closed";
|
|
1211
|
+
retryAt = s?.retryAt ?? null;
|
|
1212
|
+
}
|
|
1213
|
+
return store.saveProviderState(provider, {
|
|
1214
|
+
state,
|
|
1215
|
+
failures,
|
|
1216
|
+
retryAt,
|
|
1217
|
+
lastErrorAt: o.now,
|
|
1218
|
+
lastErrorClass: errorClass,
|
|
1219
|
+
...ring,
|
|
1220
|
+
...day,
|
|
1221
|
+
calls
|
|
1222
|
+
});
|
|
1223
|
+
}
|
|
1224
|
+
function timeoutLatency(latencyMs, budgetMs) {
|
|
1225
|
+
return Math.max(Math.round(latencyMs ?? 0), Math.round(budgetMs) + 1);
|
|
1226
|
+
}
|
|
1227
|
+
function atomically(store, fn, lockWaitMs) {
|
|
1228
|
+
if (store.atomically) return store.atomically(fn, { lockWaitMs });
|
|
1229
|
+
return lockWaitMs !== void 0 && store.withLockWait ? store.withLockWait(lockWaitMs, fn) : fn();
|
|
1230
|
+
}
|
|
1231
|
+
function ringOf(s) {
|
|
1232
|
+
const latencies = [...s?.latencies ?? []];
|
|
1233
|
+
const all = s?.latencyBudgets ?? [];
|
|
1234
|
+
const known = all.slice(Math.max(0, all.length - latencies.length));
|
|
1235
|
+
const pad = Math.max(0, latencies.length - known.length);
|
|
1236
|
+
return { latencies, latencyBudgets: [...new Array(pad).fill(0), ...known] };
|
|
1237
|
+
}
|
|
1238
|
+
function addSample(s, latencyMs, budgetMs) {
|
|
1239
|
+
const r = ringOf(s);
|
|
1240
|
+
r.latencies.push(Math.max(0, Math.round(latencyMs)));
|
|
1241
|
+
r.latencyBudgets.push(Math.max(0, Math.round(budgetMs)));
|
|
1242
|
+
return r;
|
|
1243
|
+
}
|
|
1244
|
+
function isSlow(ring) {
|
|
1245
|
+
const ratios = [];
|
|
1246
|
+
const from = Math.max(0, ring.latencies.length - 50);
|
|
1247
|
+
for (let i = from; i < ring.latencies.length; i++) {
|
|
1248
|
+
const budget = ring.latencyBudgets[i] ?? 0;
|
|
1249
|
+
const latency = ring.latencies[i];
|
|
1250
|
+
if (budget > 0 && latency !== void 0) ratios.push(latency / budget);
|
|
1251
|
+
}
|
|
1252
|
+
if (ratios.length < SLOW_MIN_SAMPLES) return false;
|
|
1253
|
+
const p2 = p95(ratios);
|
|
1254
|
+
return p2 !== void 0 && p2 > 1;
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
// src/adjudicator/config.ts
|
|
1258
|
+
init_esm_shims();
|
|
1259
|
+
|
|
1260
|
+
// src/adjudicator/secrets.ts
|
|
1261
|
+
init_esm_shims();
|
|
1262
|
+
import { randomBytes } from "crypto";
|
|
1263
|
+
import { mkdirSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync } from "fs";
|
|
1264
|
+
import { dirname } from "path";
|
|
1265
|
+
var GENERIC_KEY_ENV = "EMINIAR_ADJUDICATOR_KEY";
|
|
1266
|
+
var cached;
|
|
1267
|
+
var checksMode = () => process.platform !== "win32";
|
|
1268
|
+
function keyFileStatus() {
|
|
1269
|
+
const path = paths().adjudicatorKey;
|
|
1270
|
+
try {
|
|
1271
|
+
const mode = statSync(path).mode & 511;
|
|
1272
|
+
return { path, exists: true, mode, secure: !checksMode() || (mode & 63) === 0 };
|
|
1273
|
+
} catch {
|
|
1274
|
+
return { path, exists: false, secure: true };
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
function parseKeys(text) {
|
|
1278
|
+
try {
|
|
1279
|
+
const raw = JSON.parse(text);
|
|
1280
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return {};
|
|
1281
|
+
const out = {};
|
|
1282
|
+
for (const [k, v] of Object.entries(raw)) if (typeof v === "string" && v) out[k] = v;
|
|
1283
|
+
return out;
|
|
1284
|
+
} catch {
|
|
1285
|
+
return {};
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
function load() {
|
|
1289
|
+
if (cached) return cached;
|
|
1290
|
+
const st = keyFileStatus();
|
|
1291
|
+
if (!st.exists) cached = { keys: {}, refused: false };
|
|
1292
|
+
else if (!st.secure) cached = { keys: {}, refused: true };
|
|
1293
|
+
else {
|
|
1294
|
+
try {
|
|
1295
|
+
cached = { keys: parseKeys(readFileSync(st.path, "utf8")), refused: false };
|
|
1296
|
+
} catch {
|
|
1297
|
+
cached = { keys: {}, refused: false };
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
return cached;
|
|
1301
|
+
}
|
|
1302
|
+
function resolveKey(id, opts = {}) {
|
|
1303
|
+
const env = opts.env ?? process.env;
|
|
1304
|
+
const names = opts.fileOnly ? [] : [opts.keyEnv, opts.providerEnv, GENERIC_KEY_ENV];
|
|
1305
|
+
for (const name of names) {
|
|
1306
|
+
const v = name ? env[name]?.trim() : void 0;
|
|
1307
|
+
if (name && v) return { key: v, source: "env", envName: name };
|
|
1308
|
+
}
|
|
1309
|
+
const file = load();
|
|
1310
|
+
const key = file.keys[id];
|
|
1311
|
+
if (key) return { key, source: "file" };
|
|
1312
|
+
return file.refused ? { source: "none", refused: "permissions" } : { source: "none" };
|
|
1313
|
+
}
|
|
1314
|
+
function mask(key) {
|
|
1315
|
+
if (!key) return "none";
|
|
1316
|
+
return key.length >= 12 ? `****${key.slice(-4)}` : "****";
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
// src/adjudicator/config.ts
|
|
1320
|
+
var PRESETS = {
|
|
1321
|
+
groq: {
|
|
1322
|
+
id: "groq",
|
|
1323
|
+
baseUrl: "https://api.groq.com/openai/v1",
|
|
1324
|
+
model: "openai/gpt-oss-20b",
|
|
1325
|
+
keyEnv: "GROQ_API_KEY",
|
|
1326
|
+
price: { inPerM: 0.075, outPerM: 0.3 },
|
|
1327
|
+
local: false
|
|
1328
|
+
},
|
|
1329
|
+
cerebras: {
|
|
1330
|
+
id: "cerebras",
|
|
1331
|
+
baseUrl: "https://api.cerebras.ai/v1",
|
|
1332
|
+
model: "gpt-oss-120b",
|
|
1333
|
+
keyEnv: "CEREBRAS_API_KEY",
|
|
1334
|
+
price: null,
|
|
1335
|
+
local: false
|
|
1336
|
+
},
|
|
1337
|
+
ollama: {
|
|
1338
|
+
id: "ollama",
|
|
1339
|
+
baseUrl: "http://localhost:11434/v1",
|
|
1340
|
+
model: "qwen2.5:1.5b-instruct",
|
|
1341
|
+
price: { inPerM: 0, outPerM: 0 },
|
|
1342
|
+
local: true,
|
|
1343
|
+
extra: { keep_alive: "30m", options: { temperature: 0, num_predict: 256 } }
|
|
1344
|
+
},
|
|
1345
|
+
lmstudio: {
|
|
1346
|
+
id: "lmstudio",
|
|
1347
|
+
baseUrl: "http://localhost:1234/v1",
|
|
1348
|
+
price: { inPerM: 0, outPerM: 0 },
|
|
1349
|
+
local: true
|
|
1350
|
+
},
|
|
1351
|
+
llamacpp: {
|
|
1352
|
+
id: "llamacpp",
|
|
1353
|
+
baseUrl: "http://localhost:8080/v1",
|
|
1354
|
+
price: { inPerM: 0, outPerM: 0 },
|
|
1355
|
+
local: true
|
|
1356
|
+
},
|
|
1357
|
+
custom: { id: "custom", price: null, local: false }
|
|
1358
|
+
};
|
|
1359
|
+
var TYPESAFE_KEY_ENV = "TYPESAFE_API_KEY";
|
|
1360
|
+
var TYPESAFE_BASE_URL = "https://api.typesafe.ai/v1";
|
|
1361
|
+
function originOf(url) {
|
|
1362
|
+
if (!url) return void 0;
|
|
1363
|
+
try {
|
|
1364
|
+
const u = new URL(url);
|
|
1365
|
+
if (u.protocol !== "https:" && u.protocol !== "http:") return void 0;
|
|
1366
|
+
return u.origin;
|
|
1367
|
+
} catch {
|
|
1368
|
+
return void 0;
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
var BUILTIN_REMOTE_ORIGINS = [
|
|
1372
|
+
...Object.values(PRESETS).filter((p2) => !p2.local && p2.baseUrl).map((p2) => originOf(p2.baseUrl)),
|
|
1373
|
+
originOf(TYPESAFE_BASE_URL)
|
|
1374
|
+
];
|
|
1375
|
+
function consentFor(a, origin, env = process.env) {
|
|
1376
|
+
if (!origin || env.EMINIAR_ADJUDICATOR_REMOTE === "0" || !a.remoteConsent) return void 0;
|
|
1377
|
+
if (consentedOrigins(a).includes(origin)) return "config";
|
|
1378
|
+
if (a.remoteConsentOrigins !== void 0 || !BUILTIN_REMOTE_ORIGINS.includes(origin))
|
|
1379
|
+
return void 0;
|
|
1380
|
+
return env.EMINIAR_ADJUDICATOR_PROVIDER ? void 0 : "legacy";
|
|
1381
|
+
}
|
|
1382
|
+
function consentedOrigins(a) {
|
|
1383
|
+
const out = [];
|
|
1384
|
+
for (const s of a.remoteConsentOrigins ?? []) {
|
|
1385
|
+
const o = originOf(s);
|
|
1386
|
+
if (o && !out.includes(o)) out.push(o);
|
|
1387
|
+
}
|
|
1388
|
+
return out;
|
|
1389
|
+
}
|
|
1390
|
+
function isLoopback(url) {
|
|
1391
|
+
if (!url) return false;
|
|
1392
|
+
let host;
|
|
1393
|
+
try {
|
|
1394
|
+
host = new URL(url).hostname.toLowerCase();
|
|
1395
|
+
} catch {
|
|
1396
|
+
return false;
|
|
1397
|
+
}
|
|
1398
|
+
if (host.startsWith("[") && host.endsWith("]")) host = host.slice(1, -1);
|
|
1399
|
+
if (host === "localhost" || host.endsWith(".localhost")) return true;
|
|
1400
|
+
if (host === "::1" || host === "0:0:0:0:0:0:0:1") return true;
|
|
1401
|
+
const v4 = host.replace(/^::ffff:/, "");
|
|
1402
|
+
const m = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(v4);
|
|
1403
|
+
return !!m && m.slice(1).every((o) => Number(o) <= 255) && m[1] === "127";
|
|
1404
|
+
}
|
|
1405
|
+
function target(a) {
|
|
1406
|
+
switch (a.provider) {
|
|
1407
|
+
case "heuristic":
|
|
1408
|
+
case "embedding":
|
|
1409
|
+
return { remote: false, needsKey: false };
|
|
1410
|
+
case "typesafe":
|
|
1411
|
+
return {
|
|
1412
|
+
remote: !isLoopback(a.typesafe.baseUrl),
|
|
1413
|
+
needsKey: true,
|
|
1414
|
+
keyId: "typesafe",
|
|
1415
|
+
providerEnv: TYPESAFE_KEY_ENV,
|
|
1416
|
+
baseUrl: a.typesafe.baseUrl,
|
|
1417
|
+
model: a.typesafe.model,
|
|
1418
|
+
price: null
|
|
1419
|
+
};
|
|
1420
|
+
case "openai-compatible": {
|
|
1421
|
+
const p2 = PRESETS[a.openai.preset];
|
|
1422
|
+
const baseUrl = a.openai.baseUrl ?? p2.baseUrl;
|
|
1423
|
+
const model = a.openai.model ?? p2.model;
|
|
1424
|
+
const remote = !isLoopback(baseUrl);
|
|
1425
|
+
const t = {
|
|
1426
|
+
remote,
|
|
1427
|
+
needsKey: remote,
|
|
1428
|
+
keyId: `openai:${p2.id}`,
|
|
1429
|
+
preset: p2.id,
|
|
1430
|
+
price: a.openai.price ?? p2.price
|
|
1431
|
+
};
|
|
1432
|
+
if (p2.keyEnv) t.providerEnv = p2.keyEnv;
|
|
1433
|
+
if (baseUrl) t.baseUrl = baseUrl;
|
|
1434
|
+
if (model) t.model = model;
|
|
1435
|
+
if (p2.extra) t.extra = p2.extra;
|
|
1436
|
+
if (p2.id === "custom" && (!a.openai.baseUrl || !a.openai.model))
|
|
1437
|
+
t.misconfigured = "the custom preset needs openai.baseUrl and openai.model";
|
|
1438
|
+
return t;
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
function resolveAdjudicator(config, opts = {}) {
|
|
1443
|
+
const a = "adjudicator" in config ? config.adjudicator : config;
|
|
1444
|
+
const context = opts.context ?? "hook";
|
|
1445
|
+
const t = target(a);
|
|
1446
|
+
const key = t.keyId ? resolveKey(t.keyId, {
|
|
1447
|
+
keyEnv: a.keyEnv,
|
|
1448
|
+
providerEnv: t.providerEnv,
|
|
1449
|
+
env: opts.env,
|
|
1450
|
+
fileOnly: !t.remote
|
|
1451
|
+
}) : void 0;
|
|
1452
|
+
const r = {
|
|
1453
|
+
enabled: a.enabled,
|
|
1454
|
+
configured: a.provider,
|
|
1455
|
+
provider: a.provider,
|
|
1456
|
+
remote: t.remote,
|
|
1457
|
+
configuredRemote: t.remote,
|
|
1458
|
+
keySource: key?.source ?? "none",
|
|
1459
|
+
keyMasked: mask(key?.key),
|
|
1460
|
+
consent: a.remoteConsent,
|
|
1461
|
+
local: !t.remote && a.provider !== "heuristic"
|
|
1462
|
+
};
|
|
1463
|
+
const origin = t.remote ? originOf(t.baseUrl) : void 0;
|
|
1464
|
+
const via = t.remote ? consentFor(a, origin, opts.env) : void 0;
|
|
1465
|
+
if (t.remote) r.consent = via !== void 0;
|
|
1466
|
+
if (via) r.consentVia = via;
|
|
1467
|
+
if (origin) r.origin = origin;
|
|
1468
|
+
if (t.preset) r.preset = t.preset;
|
|
1469
|
+
if (t.baseUrl) r.baseUrl = t.baseUrl;
|
|
1470
|
+
if (t.model) r.model = t.model;
|
|
1471
|
+
if (t.price !== void 0) r.price = t.price;
|
|
1472
|
+
if (t.extra) r.extra = t.extra;
|
|
1473
|
+
if (t.keyId) r.keyId = t.keyId;
|
|
1474
|
+
if (a.keyEnv) r.keyEnv = a.keyEnv;
|
|
1475
|
+
if (t.providerEnv) r.providerEnv = t.providerEnv;
|
|
1476
|
+
if (key?.refused) r.keyFileRefused = true;
|
|
1477
|
+
const degrade = (why, detail) => ({
|
|
1478
|
+
...r,
|
|
1479
|
+
provider: "heuristic",
|
|
1480
|
+
degraded: why,
|
|
1481
|
+
detail,
|
|
1482
|
+
remote: false,
|
|
1483
|
+
local: false
|
|
1484
|
+
});
|
|
1485
|
+
if (t.misconfigured) return degrade("misconfigured", t.misconfigured);
|
|
1486
|
+
if (a.provider === "embedding" && context === "hook")
|
|
1487
|
+
return degrade("hook_context", "the embedding provider runs only in the MCP server and CLI");
|
|
1488
|
+
if (t.remote && !via)
|
|
1489
|
+
return degrade(
|
|
1490
|
+
"no_consent",
|
|
1491
|
+
`${a.provider} sends redacted prompt text to ${origin ?? hostOf(t.baseUrl)}${a.remoteConsent && origin ? ", which you have not agreed to (consent is per origin)" : ""}: run \`eminiar adjudicator enable --accept-remote\``
|
|
1492
|
+
);
|
|
1493
|
+
if (t.needsKey && key?.source !== "env" && key?.source !== "file")
|
|
1494
|
+
return degrade(
|
|
1495
|
+
"no_key",
|
|
1496
|
+
key?.refused ? "the key file is readable by others and was ignored: chmod 600 ~/.eminiar/adjudicator.key" : `no key for ${t.keyId}: run \`eminiar adjudicator key set\``
|
|
1497
|
+
);
|
|
1498
|
+
return r;
|
|
1499
|
+
}
|
|
1500
|
+
function keyFor(r, env) {
|
|
1501
|
+
if (!r.keyId) return void 0;
|
|
1502
|
+
return resolveKey(r.keyId, {
|
|
1503
|
+
keyEnv: r.keyEnv,
|
|
1504
|
+
providerEnv: r.providerEnv,
|
|
1505
|
+
env,
|
|
1506
|
+
fileOnly: !r.configuredRemote
|
|
1507
|
+
});
|
|
1508
|
+
}
|
|
1509
|
+
function currentKeyFingerprint(r, env) {
|
|
1510
|
+
const k = keyFor(r, env);
|
|
1511
|
+
return k?.key ? keyFingerprint(k.key) : null;
|
|
1512
|
+
}
|
|
1513
|
+
function hostOf(url) {
|
|
1514
|
+
if (!url) return "a remote host";
|
|
1515
|
+
try {
|
|
1516
|
+
return new URL(url).host;
|
|
1517
|
+
} catch {
|
|
1518
|
+
return "a remote host";
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
// src/adjudicator/providers/index.ts
|
|
1523
|
+
init_esm_shims();
|
|
1524
|
+
|
|
1525
|
+
// src/adjudicator/providers/openai.ts
|
|
1526
|
+
init_esm_shims();
|
|
1527
|
+
|
|
1528
|
+
// src/adjudicator/prompt.ts
|
|
1529
|
+
init_esm_shims();
|
|
1530
|
+
|
|
1531
|
+
// src/adjudicator/redact.ts
|
|
1532
|
+
init_esm_shims();
|
|
1533
|
+
import { createHash as createHash2 } from "crypto";
|
|
1534
|
+
function normalize(text) {
|
|
1535
|
+
return text.normalize("NFKC").replace(/\r\n?/g, "\n").replace(/[ \t]+$/gm, "").replace(/[ \t]{2,}/g, " ").replace(/\n{3,}/g, "\n\n").trim();
|
|
1536
|
+
}
|
|
1537
|
+
function inputHash(text) {
|
|
1538
|
+
return createHash2("sha256").update(normalize(text)).digest("hex");
|
|
1539
|
+
}
|
|
1540
|
+
var SECRET_WORDS = /* @__PURE__ */ new Set([
|
|
1541
|
+
"key",
|
|
1542
|
+
"token",
|
|
1543
|
+
"secret",
|
|
1544
|
+
"secrets",
|
|
1545
|
+
"pass",
|
|
1546
|
+
"password",
|
|
1547
|
+
"passwd",
|
|
1548
|
+
"passphrase",
|
|
1549
|
+
"pwd",
|
|
1550
|
+
"pw",
|
|
1551
|
+
"credential",
|
|
1552
|
+
"credentials",
|
|
1553
|
+
"creds",
|
|
1554
|
+
"auth",
|
|
1555
|
+
"authorization",
|
|
1556
|
+
"pat"
|
|
1557
|
+
]);
|
|
1558
|
+
var SECRET_WORD_SUFFIX = /(?:password|passwd|passphrase|secret|token|apikey|accesskey|secretkey|privatekey|authkey|signingkey|masterkey)$/;
|
|
1559
|
+
var SECRET_QUALIFIERS = /* @__PURE__ */ new Set([
|
|
1560
|
+
"base",
|
|
1561
|
+
"hash",
|
|
1562
|
+
"value",
|
|
1563
|
+
"val",
|
|
1564
|
+
"b64",
|
|
1565
|
+
"base64",
|
|
1566
|
+
"enc",
|
|
1567
|
+
"encrypted",
|
|
1568
|
+
"raw",
|
|
1569
|
+
"plain",
|
|
1570
|
+
"str",
|
|
1571
|
+
"string",
|
|
1572
|
+
"live",
|
|
1573
|
+
"prod",
|
|
1574
|
+
"test",
|
|
1575
|
+
"data"
|
|
1576
|
+
]);
|
|
1577
|
+
function isSecretName(name) {
|
|
1578
|
+
const words = name.split(/[_.-]+|(?<=[a-z0-9])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])/).map((w) => w.toLowerCase()).filter(Boolean);
|
|
1579
|
+
for (let i = words.length - 1; i >= 0; i--) {
|
|
1580
|
+
const w = words[i];
|
|
1581
|
+
if (SECRET_WORDS.has(w) || SECRET_WORD_SUFFIX.test(w)) return true;
|
|
1582
|
+
if (!SECRET_QUALIFIERS.has(w) && !/^v?\d+$/.test(w)) return false;
|
|
1583
|
+
}
|
|
1584
|
+
return false;
|
|
1585
|
+
}
|
|
1586
|
+
var NOT_A_SECRET_VALUE = /^(?:true|false|null|nil|none|undefined|yes|no|on|off|\*+|\[[a-z ]+\]|\$\{?[A-Za-z_][A-Za-z0-9_]*\}?|process\.env\.[A-Za-z_][A-Za-z0-9_]*|<[^>]*>|\d{1,4})$/i;
|
|
1587
|
+
var ASSIGN_NAME = /(?<![A-Za-z0-9_.-])([A-Za-z0-9_.-]+)(["'`]?[ \t]*(?::=|=>|[:=])[ \t]*)/g;
|
|
1588
|
+
var ASSIGN_VALUE = new RegExp(
|
|
1589
|
+
[
|
|
1590
|
+
"(?!(?:Basic|Bearer|Token|Digest|Negotiate|NTLM)[ \\t]+\\*\\*\\*)",
|
|
1591
|
+
"(?:",
|
|
1592
|
+
"([\"'`])((?:(?!\\1)[^\\\\\\n]|\\\\.){0,512})\\1",
|
|
1593
|
+
// 1 quote, 2 quoted value
|
|
1594
|
+
"|",
|
|
1595
|
+
"([^\\s\"'`&;,=<\\[{(|>](?:[^\\s\"'`&;,]|[&;,](?=[^\\s\"'`&;,])(?![A-Za-z0-9_.\\- ]*=))*)",
|
|
1596
|
+
// 3 bare
|
|
1597
|
+
")"
|
|
1598
|
+
].join(""),
|
|
1599
|
+
"y"
|
|
1600
|
+
);
|
|
1601
|
+
function maskAssignments(text) {
|
|
1602
|
+
ASSIGN_NAME.lastIndex = 0;
|
|
1603
|
+
let out = "";
|
|
1604
|
+
let last = 0;
|
|
1605
|
+
for (let m = ASSIGN_NAME.exec(text); m; m = ASSIGN_NAME.exec(text)) {
|
|
1606
|
+
if (!isSecretName(m[1])) continue;
|
|
1607
|
+
const start = m.index + m[0].length;
|
|
1608
|
+
ASSIGN_VALUE.lastIndex = start;
|
|
1609
|
+
const v = ASSIGN_VALUE.exec(text);
|
|
1610
|
+
if (!v) continue;
|
|
1611
|
+
const [value, q, quoted, bare] = v;
|
|
1612
|
+
const raw = q ? quoted ?? "" : bare ?? "";
|
|
1613
|
+
if (!raw || NOT_A_SECRET_VALUE.test(raw)) continue;
|
|
1614
|
+
out += text.slice(last, start) + (q ? `${q}***${q}` : "***");
|
|
1615
|
+
last = start + value.length;
|
|
1616
|
+
ASSIGN_NAME.lastIndex = last;
|
|
1617
|
+
}
|
|
1618
|
+
return out + text.slice(last);
|
|
1619
|
+
}
|
|
1620
|
+
function maskYamlBlocks(text) {
|
|
1621
|
+
if (!/:[ \t]*[|>]/.test(text)) return text;
|
|
1622
|
+
const lines = text.split("\n");
|
|
1623
|
+
const header = /^([ \t]*)(?:-[ \t]+)?["']?([A-Za-z0-9_.-]+)["']?[ \t]*:[ \t]*[|>][+-]?[1-9]?[ \t\r]*$/;
|
|
1624
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1625
|
+
const h = header.exec(lines[i]);
|
|
1626
|
+
if (!h || !isSecretName(h[2])) continue;
|
|
1627
|
+
const indent = h[1].length;
|
|
1628
|
+
let last = i;
|
|
1629
|
+
let pad = "";
|
|
1630
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
1631
|
+
const line = lines[j];
|
|
1632
|
+
if (line.trim() === "") continue;
|
|
1633
|
+
const n = /^[ \t]*/.exec(line)[0].length;
|
|
1634
|
+
if (n <= indent) break;
|
|
1635
|
+
if (last === i) pad = line.slice(0, n);
|
|
1636
|
+
last = j;
|
|
1637
|
+
}
|
|
1638
|
+
if (last === i) continue;
|
|
1639
|
+
lines.splice(i + 1, last - i, `${pad}***`);
|
|
1640
|
+
i++;
|
|
1641
|
+
}
|
|
1642
|
+
return lines.join("\n");
|
|
1643
|
+
}
|
|
1644
|
+
var COOKIE_ATTRIBUTES = /* @__PURE__ */ new Set([
|
|
1645
|
+
"path",
|
|
1646
|
+
"domain",
|
|
1647
|
+
"expires",
|
|
1648
|
+
"max-age",
|
|
1649
|
+
"samesite",
|
|
1650
|
+
"secure",
|
|
1651
|
+
"httponly",
|
|
1652
|
+
"priority",
|
|
1653
|
+
"partitioned",
|
|
1654
|
+
"version",
|
|
1655
|
+
"comment"
|
|
1656
|
+
]);
|
|
1657
|
+
function maskCookiePairs(pairs) {
|
|
1658
|
+
return pairs.replace(
|
|
1659
|
+
/(^|;[ \t]*)([^=;]+)=([^;]*)/g,
|
|
1660
|
+
(m, sep, name, value) => !value || value === "***" || COOKIE_ATTRIBUTES.has(name.trim().toLowerCase()) ? m : `${sep}${name}=***`
|
|
1661
|
+
);
|
|
1662
|
+
}
|
|
1663
|
+
var IGNORABLE = new RegExp("\\p{Default_Ignorable_Code_Point}", "gu");
|
|
1664
|
+
function clean(text) {
|
|
1665
|
+
return text.replace(IGNORABLE, "").normalize("NFKC").replace(IGNORABLE, "");
|
|
1666
|
+
}
|
|
1667
|
+
var RULES = [
|
|
1668
|
+
// PEM / OpenSSH / PGP private key blocks, whole (to the END line, or the end of the text).
|
|
1669
|
+
[
|
|
1670
|
+
/-----BEGIN [A-Z0-9 ]{0,40}PRIVATE KEY(?: BLOCK)?-----[\s\S]*?(?:-----END [A-Z0-9 ]{0,60}-----|$)/g,
|
|
1671
|
+
"[private key]"
|
|
1672
|
+
],
|
|
1673
|
+
// Credentials inside URLs: scheme://user:pass@host, scheme://:pass@host
|
|
1674
|
+
[/\b([a-z][a-z0-9+.-]{0,30}:\/\/)[^\s/:@]*:[^\s/@]+@/gi, "$1***@"],
|
|
1675
|
+
// Authorization / Proxy-Authorization headers: the scheme stays, the credentials go.
|
|
1676
|
+
[
|
|
1677
|
+
/\b((?:Proxy-)?Authorization["']?[ \t]*[:=][ \t]*["']?)(?!(?:(?:Basic|Bearer|Token|Digest|Negotiate|NTLM|ApiKey|Key)[ \t]+)?\*\*\*)(?:(Basic|Bearer|Token|Digest|Negotiate|NTLM|ApiKey|Key)[ \t]+)?[^\s"'`]+/gi,
|
|
1678
|
+
(_m, pre = "", scheme = "") => `${pre}${scheme ? `${scheme} ` : ""}***`
|
|
1679
|
+
],
|
|
1680
|
+
[/\bBearer[ \t]+(?!\*\*\*)[A-Za-z0-9._~+/=-]{8,}/gi, "Bearer ***"],
|
|
1681
|
+
// curl -u / --user user:pass (the user stays)
|
|
1682
|
+
[/(?<!\S)(-u|--user)([ \t]*=?[ \t]*)(["']?)([^\s:"'@]+):(?!\*\*\*)[^\s"']+\3/g, "$1$2$3$4:***$3"],
|
|
1683
|
+
// --password x, --token x, --api-key x, --client-secret x … (the `=` form is an assignment)
|
|
1684
|
+
[
|
|
1685
|
+
/(?<!\S)(--(?:[a-z0-9]+-){0,4}(?:password|passwd|pass|passphrase|token|secret|api-?key|access-key|secret-key|auth-token|credentials?))([ \t]+)(?!-|\*\*\*)(?:(["'])[^\n]{0,256}?\3|[^\s"']+)/gi,
|
|
1686
|
+
"$1$2$3***$3"
|
|
1687
|
+
],
|
|
1688
|
+
// mysql / mariadb -p<password> (attached; `-p` alone prompts)
|
|
1689
|
+
[
|
|
1690
|
+
/\b((?:mysql|mysqldump|mysqladmin|mariadb|mariadb-dump)\b[^\n]{0,300}?(?<!\S)-p)(?![\s*])(?:(["'])[^\n]{0,256}?\2|[^\s"']+)/g,
|
|
1691
|
+
"$1$2***$2"
|
|
1692
|
+
],
|
|
1693
|
+
// Tools whose short password flag takes the next word, only on that tool's own command (the
|
|
1694
|
+
// context stops at ; & | so `docker login … && docker run -p 8080:80` keeps the port):
|
|
1695
|
+
// registry logins -p, mongo* -p, redis-cli -a, ldap* -w, zip/unzip -P, mosquitto_* -P.
|
|
1696
|
+
[
|
|
1697
|
+
/\b((?:(?:(?:docker|podman|nerdctl|buildah|skopeo|oras|finch)(?:[ \t]+registry)?|helm[ \t]+registry|crane[ \t]+auth)[ \t]+login\b[^\n;&|]{0,300}?(?<!\S)-p|mongo(?:sh|dump|restore|import|export|stat|top|files)?\b[^\n;&|]{0,300}?(?<!\S)-p|redis-cli\b[^\n;&|]{0,300}?(?<!\S)-a|ldap(?:search|add|modify|delete|whoami|passwd|compare|modrdn|exop)\b[^\n;&|]{0,300}?(?<!\S)-w|(?:zip|unzip|zipcloak)\b[^\n;&|]{0,300}?(?<!\S)-P|mosquitto_(?:pub|sub|rr)\b[^\n;&|]{0,300}?(?<!\S)-P)[ \t]+)(?!-|\*\*\*)(?:(["'])[^\n]{0,256}?\2|[^\s"']+)/g,
|
|
1698
|
+
"$1$2***$2"
|
|
1699
|
+
],
|
|
1700
|
+
// 7z -p<password> (attached, like mysql)
|
|
1701
|
+
[
|
|
1702
|
+
/\b(7z[az]?\b[^\n;&|]{0,300}?(?<!\S)-p)(?![\s*])(?:(["'])[^\n]{0,256}?\2|[^\s"']+)/g,
|
|
1703
|
+
"$1$2***$2"
|
|
1704
|
+
],
|
|
1705
|
+
// sshpass -p <password> / -p<password>: only sshpass's own options may come before it, so
|
|
1706
|
+
// `sshpass -f file ssh -p 22 host` keeps ssh's port.
|
|
1707
|
+
[
|
|
1708
|
+
/\b(sshpass(?:[ \t]+(?:-[fdP][ \t]*[^\s-]\S*|-[a-oq-zA-Z]\S*)){0,3}[ \t]+-p[ \t]*)(?!-|\*\*\*)(?:(["'])[^\n]{0,256}?\2|[^\s"']+)/g,
|
|
1709
|
+
"$1$2***$2"
|
|
1710
|
+
],
|
|
1711
|
+
// htpasswd -b[n] [file] user password: the last word is the password.
|
|
1712
|
+
[
|
|
1713
|
+
/\b(htpasswd((?:[ \t]+-[A-Za-z]+){1,4})[ \t]+)([^\s-]\S*)[ \t]+([^\s-]\S*)(?:[ \t]+([^\s-]\S*))?/g,
|
|
1714
|
+
(m, pre = "", flags = "", a = "", b = "", c) => {
|
|
1715
|
+
if (!/b/.test(flags)) return m;
|
|
1716
|
+
if (/n/.test(flags))
|
|
1717
|
+
return b === "***" ? m : `${pre}${a} ***${c === void 0 ? "" : ` ${c}`}`;
|
|
1718
|
+
return c === void 0 || c === "***" ? m : `${pre}${a} ${b} ***`;
|
|
1719
|
+
}
|
|
1720
|
+
],
|
|
1721
|
+
// echo <password> | docker login --password-stdin, and … --password-stdin <<< <password>
|
|
1722
|
+
[
|
|
1723
|
+
/(?<!\S)((?:echo(?:[ \t]+-[neE]+)?|printf(?:[ \t]+(["'])%s(?:\\n)?\2)?)[ \t]+)(["']?)(?!\$|\*\*\*)[^\s"'|]{1,256}\3(?=[ \t]*\|[^\n]{0,200}?--password-stdin\b)/g,
|
|
1724
|
+
"$1$3***$3"
|
|
1725
|
+
],
|
|
1726
|
+
[/(--password-stdin\b[^\n]{0,200}?<<<[ \t]*)(["']?)(?!\$|\*\*\*)[^\s"']{1,256}\2/g, "$1$2***$2"],
|
|
1727
|
+
// openssl -pass/-passin/-passout pass:<password> (env:/file:/fd: name a source, kept)
|
|
1728
|
+
[/(?<!\S)(-pass(?:in|out)?[ \t]+pass:)(?!\*\*\*)[^\s"']+/g, "$1***"],
|
|
1729
|
+
// keytool -storepass x / -keypass x (… -storepass:env VAR names a source, kept)
|
|
1730
|
+
[
|
|
1731
|
+
/(?<!\S)(-(?:store|key|srcstore|deststore|srckey|destkey)pass[ \t]+)(?!-|\*\*\*)(?:(["'])[^\n]{0,256}?\2|[^\s"']+)/g,
|
|
1732
|
+
"$1$2***$2"
|
|
1733
|
+
],
|
|
1734
|
+
// smbclient / rpcclient -U user%password (the user stays)
|
|
1735
|
+
[/(?<!\S)(-U[ \t]*)(["']?)([^\s%"']+)%(?!\*\*\*)[^\s"']+\2/g, "$1$2$3%***$2"],
|
|
1736
|
+
// .netrc: `machine host login me password x` on one line or across lines, `default …`, and a
|
|
1737
|
+
// bare `login me` / `password x` pair of lines.
|
|
1738
|
+
[
|
|
1739
|
+
/((?:\bmachine[ \t]+[^\s]+|^[ \t]*default\b)(?:\s+(?:login|account)[ \t]+[^\s]+){0,2}\s+password[ \t]+|^[ \t]*login[ \t]+[^\s]+[ \t]*\n[ \t]*password[ \t]+)(?!\*\*\*)("[^"\n]{0,256}"|[^\s"]+)/gm,
|
|
1740
|
+
(_m, pre = "", v = "") => `${pre}${v.startsWith('"') ? '"***"' : "***"}`
|
|
1741
|
+
],
|
|
1742
|
+
// Cookie / Set-Cookie headers (and curl --cookie / -b): every cookie value goes, the names and
|
|
1743
|
+
// the Set-Cookie attributes (Path, Expires, …) stay.
|
|
1744
|
+
[
|
|
1745
|
+
/(\b(?:Set-)?Cookie["']?[ \t]*:[ \t]*["']?|(?:\bcurl\b[^\n;&|]{0,300}?(?<!\S)-b|(?<!\S)--cookie)(?:[ \t]+|=)["']?)([^\s=;,"'`]+=[^\s;,"'`]*(?:;[ \t]*[^\s=;,"'`]+(?:=[^\s;,"'`]*)?)*)/gi,
|
|
1746
|
+
(_m, pre = "", pairs = "") => pre + maskCookiePairs(pairs)
|
|
1747
|
+
],
|
|
1748
|
+
// Prose: "the password is Wint3rIsComing", "the signing secret was x9-y" (only when the word
|
|
1749
|
+
// looks like a secret: it has a digit or a symbol).
|
|
1750
|
+
[
|
|
1751
|
+
/\b((?:pass(?:word|phrase|wd)|secret|token|api[ -]?key|access[ -]key)[ \t]+(?:is|was)[ \t]*:?[ \t]+)(["']?)(?=[^\s"']{0,128}[\d!@#$%^&*])(?!\*\*\*)[^\s"']{4,128}\2/gi,
|
|
1752
|
+
"$1$2***$2"
|
|
1753
|
+
],
|
|
1754
|
+
[/\b(sk|rk|pk)_(live|test)_[A-Za-z0-9]{10,}/g, "$1_$2_***"],
|
|
1755
|
+
[/\bwhsec_[A-Za-z0-9]{16,}/g, "whsec_***"],
|
|
1756
|
+
[/\bsk-[A-Za-z0-9_-]{16,}/g, "sk-***"],
|
|
1757
|
+
[/\b(gh[pousr]|github_pat)_[A-Za-z0-9_]{20,}/g, "$1_***"],
|
|
1758
|
+
[/\bglpat-[A-Za-z0-9_-]{20,}/g, "glpat-***"],
|
|
1759
|
+
[/\bnpm_[A-Za-z0-9]{36,}/g, "npm_***"],
|
|
1760
|
+
[/\bhf_[A-Za-z0-9]{30,}/g, "hf_***"],
|
|
1761
|
+
[/\bxox[a-z]-[A-Za-z0-9-]{10,}/g, "xox*-***"],
|
|
1762
|
+
[/\b(hooks\.slack\.com\/services\/)[A-Za-z0-9/]{10,}/g, "$1***"],
|
|
1763
|
+
[/\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/g, "AKIA***"],
|
|
1764
|
+
[/\bAIza[0-9A-Za-z_-]{35}/g, "AIza***"],
|
|
1765
|
+
[/\bSG\.[A-Za-z0-9_-]{16,}\.[A-Za-z0-9_-]{16,}/g, "SG.***"],
|
|
1766
|
+
[/\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/g, "[jwt]"],
|
|
1767
|
+
["assignments", maskAssignments],
|
|
1768
|
+
// (the local part starts at the start of its run, not at every \b inside it: `--a-a-a…`)
|
|
1769
|
+
[/(?<![A-Za-z0-9._%+-])[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g, "[email]"],
|
|
1770
|
+
[/\b[0-9a-fA-F]{24,}\b/g, "[hex]"],
|
|
1771
|
+
// base64 / base64url runs of 32+ chars mixing upper, lower and digits (not a word or a path)
|
|
1772
|
+
[
|
|
1773
|
+
/(?<![A-Za-z0-9+/_-])(?=[A-Za-z0-9+/_-]*\d)(?=[A-Za-z0-9+/_-]*[A-Z])(?=[A-Za-z0-9+/_-]*[a-z])[A-Za-z0-9+/_-]{32,}={0,2}/g,
|
|
1774
|
+
"[b64]"
|
|
1775
|
+
]
|
|
1776
|
+
];
|
|
1777
|
+
function redact(text) {
|
|
1778
|
+
let out = maskYamlBlocks(clean(text));
|
|
1779
|
+
for (const rule of RULES) {
|
|
1780
|
+
if (rule[0] === "assignments") out = rule[1](out);
|
|
1781
|
+
else if (typeof rule[1] === "string") out = out.replace(rule[0], rule[1]);
|
|
1782
|
+
else out = out.replace(rule[0], rule[1]);
|
|
1783
|
+
}
|
|
1784
|
+
return out;
|
|
1785
|
+
}
|
|
1786
|
+
function head(text, n = 80) {
|
|
1787
|
+
const chars = Array.from(redact(normalize(maskYamlBlocks(clean(text)))).replace(/\s+/g, " "));
|
|
1788
|
+
if (chars.length <= n) return chars.join("");
|
|
1789
|
+
return `${chars.slice(0, Math.max(0, n - 1)).join("")}\u2026`;
|
|
1790
|
+
}
|
|
1791
|
+
function forRemote(text, maxChars) {
|
|
1792
|
+
return Array.from(redact(normalize(maskYamlBlocks(clean(text))))).slice(0, maxChars).join("");
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1795
|
+
// src/adjudicator/prompt.ts
|
|
1796
|
+
var COMPLEXITY_CRITERIA = [
|
|
1797
|
+
"trivial chat or lookup",
|
|
1798
|
+
"small single-file change",
|
|
1799
|
+
"multi-step change in one area",
|
|
1800
|
+
"cross-cutting change",
|
|
1801
|
+
"architectural or multi-subsystem"
|
|
1802
|
+
];
|
|
1803
|
+
var TIER_CRITERIA = {
|
|
1804
|
+
haiku: "fast and cheap: lookups, searches, small mechanical edits, summaries",
|
|
1805
|
+
sonnet: "default coder: features and fixes in one area, tests, ordinary reviews",
|
|
1806
|
+
opus: "hard reasoning: cross-cutting or architectural work, subtle debugging, design",
|
|
1807
|
+
fable: "largest model: only the hardest open-ended, multi-subsystem problems"
|
|
1808
|
+
};
|
|
1809
|
+
var ROLE_DESCRIPTIONS = {
|
|
1810
|
+
explorer: "finds where code lives and what depends on it",
|
|
1811
|
+
researcher: "reads documentation and external sources",
|
|
1812
|
+
coder: "implements and edits code",
|
|
1813
|
+
tester: "writes and runs tests",
|
|
1814
|
+
reviewer: "reviews changes for bugs and quality",
|
|
1815
|
+
architect: "designs and plans larger changes",
|
|
1816
|
+
ui: "builds user interface work",
|
|
1817
|
+
none: "no delegation needed"
|
|
1818
|
+
};
|
|
1819
|
+
var TOOL_DESCRIPTIONS = {
|
|
1820
|
+
em_symbols: "find a symbol or list a file's symbols",
|
|
1821
|
+
em_search: "search indexed code, docs and past output",
|
|
1822
|
+
em_graph_impact: "what depends on or breaks from a change",
|
|
1823
|
+
em_history: "git history of a file or change",
|
|
1824
|
+
em_execute: "run a command or tests, keep output small",
|
|
1825
|
+
em_batch_execute: "run several commands in one call, keep output small",
|
|
1826
|
+
em_execute_file: "process a large file without reading it",
|
|
1827
|
+
em_docs: "answer from indexed library docs",
|
|
1828
|
+
em_fetch_and_index: "fetch and index a web page",
|
|
1829
|
+
em_recall: "recall past decisions and constraints",
|
|
1830
|
+
em_swarm: "plan and fan out work to agents",
|
|
1831
|
+
Edit: "edit a file directly",
|
|
1832
|
+
Bash: "run a short shell command",
|
|
1833
|
+
Agent: "delegate to a subagent",
|
|
1834
|
+
none: "no tool, just answer"
|
|
1835
|
+
};
|
|
1836
|
+
function itemId(i) {
|
|
1837
|
+
return `i${i}`;
|
|
1838
|
+
}
|
|
1839
|
+
var MAX_ROLES = 16;
|
|
1840
|
+
var ROLE_NAME = /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,63}$/;
|
|
1841
|
+
function buildCatalogue(reqs, o) {
|
|
1842
|
+
const roles = /* @__PURE__ */ new Map();
|
|
1843
|
+
for (const r of reqs)
|
|
1844
|
+
for (const role of r.hints?.roles ?? []) {
|
|
1845
|
+
if (roles.size >= MAX_ROLES - 1) break;
|
|
1846
|
+
if (typeof role.name === "string" && ROLE_NAME.test(role.name) && !roles.has(role.name))
|
|
1847
|
+
roles.set(role.name, typeof role.description === "string" ? role.description : "");
|
|
1848
|
+
}
|
|
1849
|
+
if (roles.size === 0) for (const r of DEFAULT_ROLES) roles.set(r, ROLE_DESCRIPTIONS[r]);
|
|
1850
|
+
else if (!roles.has("none")) roles.set("none", ROLE_DESCRIPTIONS.none);
|
|
1851
|
+
const allowed = o.tools ? new Set(o.tools) : void 0;
|
|
1852
|
+
const tools = TOOL_IDS.filter((t) => !allowed || allowed.has(t) || t === "none");
|
|
1853
|
+
const tiers = [...new Set(o.tiers)].filter((t) => t in TIER_CRITERIA);
|
|
1854
|
+
return {
|
|
1855
|
+
tiers: tiers.map((t) => ({ name: t, description: TIER_CRITERIA[t] })),
|
|
1856
|
+
roles: [...roles].map(([name, description]) => ({ name, description: oneLine(description) })),
|
|
1857
|
+
tools: tools.map((t) => ({ name: t, description: TOOL_DESCRIPTIONS[t] }))
|
|
1858
|
+
};
|
|
1859
|
+
}
|
|
1860
|
+
function buildItems(reqs, maxChars, offset = 0) {
|
|
1861
|
+
return reqs.map((r, i) => {
|
|
1862
|
+
const item = { id: itemId(offset + i), text: forRemote(r.text, maxChars) };
|
|
1863
|
+
if (r.hints?.role) item.role = r.hints.role;
|
|
1864
|
+
if (r.hints?.files !== void 0) item.files = r.hints.files;
|
|
1865
|
+
if (r.hints?.deps !== void 0) item.deps = r.hints.deps;
|
|
1866
|
+
return item;
|
|
1867
|
+
});
|
|
1868
|
+
}
|
|
1869
|
+
function jevQuestionId(item, q) {
|
|
1870
|
+
return `${item}.${q}`;
|
|
1871
|
+
}
|
|
1872
|
+
function jevState(cat, items) {
|
|
1873
|
+
return {
|
|
1874
|
+
catalogue: {
|
|
1875
|
+
tiers: cat.tiers.map((t) => t.name),
|
|
1876
|
+
roles: cat.roles,
|
|
1877
|
+
tools: cat.tools.map((t) => t.name)
|
|
1878
|
+
},
|
|
1879
|
+
items
|
|
1880
|
+
};
|
|
1881
|
+
}
|
|
1882
|
+
function jevQuestions(items, cat, o) {
|
|
1883
|
+
const tierCriteria = Object.fromEntries(cat.tiers.map((t) => [t.name, t.description]));
|
|
1884
|
+
const roleCriteria = Object.fromEntries(
|
|
1885
|
+
cat.roles.map((r) => [r.name, r.description || `the ${r.name} role`])
|
|
1886
|
+
);
|
|
1887
|
+
const toolCriteria = Object.fromEntries(cat.tools.map((t) => [t.name, t.description]));
|
|
1888
|
+
const out = {};
|
|
1889
|
+
for (const { id } of items) {
|
|
1890
|
+
const it = `item "${id}" in state.items`;
|
|
1891
|
+
out[jevQuestionId(id, "complexity")] = {
|
|
1892
|
+
type: "score",
|
|
1893
|
+
instructions: `How complex is the software work described by ${it}?`,
|
|
1894
|
+
criteria: [...COMPLEXITY_CRITERIA]
|
|
1895
|
+
};
|
|
1896
|
+
out[jevQuestionId(id, "tier")] = {
|
|
1897
|
+
type: "choice",
|
|
1898
|
+
instructions: `Which Claude model tier is the smallest that can do ${it} well?`,
|
|
1899
|
+
criteria: tierCriteria
|
|
1900
|
+
};
|
|
1901
|
+
out[jevQuestionId(id, "role")] = {
|
|
1902
|
+
type: "choice",
|
|
1903
|
+
instructions: `Which agent role (state.catalogue.roles) fits ${it} best?`,
|
|
1904
|
+
criteria: roleCriteria
|
|
1905
|
+
};
|
|
1906
|
+
out[jevQuestionId(id, "risky")] = {
|
|
1907
|
+
type: "noul",
|
|
1908
|
+
instructions: `Is ${it} risky: could it break shared code, data, security or a release?`
|
|
1909
|
+
};
|
|
1910
|
+
out[jevQuestionId(id, "needs_plan")] = {
|
|
1911
|
+
type: "noul",
|
|
1912
|
+
instructions: `Does ${it} need a plan split into several tasks before any code is written?`
|
|
1913
|
+
};
|
|
1914
|
+
if (o.askTool)
|
|
1915
|
+
out[jevQuestionId(id, "tool")] = {
|
|
1916
|
+
type: "choice",
|
|
1917
|
+
instructions: `Which tool should be used first for ${it}?`,
|
|
1918
|
+
criteria: toolCriteria
|
|
1919
|
+
};
|
|
1920
|
+
}
|
|
1921
|
+
return out;
|
|
1922
|
+
}
|
|
1923
|
+
var SCHEMA_MAX_CHARS = 5e3;
|
|
1924
|
+
function openaiSchema(cat, ids, o) {
|
|
1925
|
+
const item = {
|
|
1926
|
+
id: { type: "string", enum: [...ids] },
|
|
1927
|
+
complexity: { type: "integer", enum: [0, 1, 2, 3, 4] },
|
|
1928
|
+
tier: { type: "string", enum: cat.tiers.map((t) => t.name) },
|
|
1929
|
+
role: { type: "string", enum: cat.roles.map((r) => r.name) }
|
|
1930
|
+
};
|
|
1931
|
+
if (o.askTool) item.tool = { type: "string", enum: cat.tools.map((t) => t.name) };
|
|
1932
|
+
item.risky = { type: "boolean" };
|
|
1933
|
+
item.confidence = { type: "number" };
|
|
1934
|
+
const schema = {
|
|
1935
|
+
type: "object",
|
|
1936
|
+
properties: {
|
|
1937
|
+
items: {
|
|
1938
|
+
type: "array",
|
|
1939
|
+
items: {
|
|
1940
|
+
type: "object",
|
|
1941
|
+
properties: item,
|
|
1942
|
+
required: Object.keys(item),
|
|
1943
|
+
additionalProperties: false
|
|
1944
|
+
}
|
|
1945
|
+
}
|
|
1946
|
+
},
|
|
1947
|
+
required: ["items"],
|
|
1948
|
+
additionalProperties: false
|
|
1949
|
+
};
|
|
1950
|
+
if (schemaText(schema).length > SCHEMA_MAX_CHARS) item.id = { type: "string" };
|
|
1951
|
+
return schema;
|
|
1952
|
+
}
|
|
1953
|
+
function schemaText(schema) {
|
|
1954
|
+
return JSON.stringify(schema);
|
|
1955
|
+
}
|
|
1956
|
+
function rubric(cat, o) {
|
|
1957
|
+
const lines = [
|
|
1958
|
+
"You route software-engineering requests for a coding assistant. For each item in the user's JSON, judge the work it describes.",
|
|
1959
|
+
"complexity (integer):",
|
|
1960
|
+
...COMPLEXITY_CRITERIA.map((c, i) => ` ${i} = ${c}`),
|
|
1961
|
+
"tier: the smallest Claude model that does the item well:",
|
|
1962
|
+
...cat.tiers.map((t) => ` ${t.name}: ${t.description}`),
|
|
1963
|
+
"role: the agent role that fits best:",
|
|
1964
|
+
...cat.roles.map((r) => ` ${r.name}${r.description ? `: ${r.description}` : ""}`)
|
|
1965
|
+
];
|
|
1966
|
+
if (o.askTool)
|
|
1967
|
+
lines.push(
|
|
1968
|
+
"tool: the tool to use first:",
|
|
1969
|
+
...cat.tools.map((t) => ` ${t.name}: ${t.description}`)
|
|
1970
|
+
);
|
|
1971
|
+
lines.push(
|
|
1972
|
+
"risky: true when the work could break shared code, data, security or a release.",
|
|
1973
|
+
"confidence: 0..1, how sure you are of tier and complexity.",
|
|
1974
|
+
"Answer every item, once, with its id. Reply with JSON only."
|
|
1975
|
+
);
|
|
1976
|
+
if (o.schema !== void 0)
|
|
1977
|
+
lines.push(`The JSON must match this schema: ${schemaText(o.schema)}`);
|
|
1978
|
+
return lines.join("\n");
|
|
1979
|
+
}
|
|
1980
|
+
function oneLine(s) {
|
|
1981
|
+
const t = s.replace(/\s+/g, " ").trim();
|
|
1982
|
+
return t.length > 120 ? `${t.slice(0, 119)}\u2026` : t;
|
|
1983
|
+
}
|
|
1984
|
+
|
|
1985
|
+
// src/adjudicator/providers/http.ts
|
|
1986
|
+
init_esm_shims();
|
|
1987
|
+
|
|
1988
|
+
// src/adjudicator/providers/capture.ts
|
|
1989
|
+
init_esm_shims();
|
|
1990
|
+
var EXCHANGE_BODY_MAX = 65536;
|
|
1991
|
+
var SCRUB_WINDOW = EXCHANGE_BODY_MAX * 2;
|
|
1992
|
+
var MAX_DEPTH = 64;
|
|
1993
|
+
var SECRET_HEADERS = /* @__PURE__ */ new Set(["cookie", "set-cookie", "proxy-authorization"]);
|
|
1994
|
+
var IGNORABLE2 = new RegExp("\\p{Default_Ignorable_Code_Point}", "gu");
|
|
1995
|
+
function unhide(s) {
|
|
1996
|
+
return s.replace(IGNORABLE2, "").normalize("NFKC").replace(IGNORABLE2, "");
|
|
1997
|
+
}
|
|
1998
|
+
function replaceAll(s, needle) {
|
|
1999
|
+
return s.includes(needle) ? s.split(needle).join("***") : s;
|
|
2000
|
+
}
|
|
2001
|
+
function scrubKey(s, key) {
|
|
2002
|
+
if (!key || key.length < 4) return redact(s);
|
|
2003
|
+
const plain = unhide(key);
|
|
2004
|
+
let out = unhide(replaceAll(s, key));
|
|
2005
|
+
if (plain.length >= 4) out = replaceAll(out, plain);
|
|
2006
|
+
out = redact(out);
|
|
2007
|
+
return plain.length >= 4 ? replaceAll(out, plain) : out;
|
|
2008
|
+
}
|
|
2009
|
+
function dropKeyTail(s, key) {
|
|
2010
|
+
if (!key || key.length < 4) return s;
|
|
2011
|
+
const plain = unhide(key);
|
|
2012
|
+
for (let n = Math.min(plain.length - 1, s.length); n > 0; n--)
|
|
2013
|
+
if (s.endsWith(plain.slice(0, n))) return s.slice(0, s.length - n);
|
|
2014
|
+
return s;
|
|
2015
|
+
}
|
|
2016
|
+
function scrubHead(text, window, key) {
|
|
2017
|
+
if (text.length <= window) return scrubKey(text, key);
|
|
2018
|
+
return dropKeyTail(scrubKey(text.slice(0, window), key), key);
|
|
2019
|
+
}
|
|
2020
|
+
function trivial(s) {
|
|
2021
|
+
return s === "" || /^\*+$/.test(s);
|
|
2022
|
+
}
|
|
2023
|
+
function walk(v, key, depth, secret) {
|
|
2024
|
+
if (typeof v === "string") return secret && !trivial(v) ? "***" : scrubKey(v, key);
|
|
2025
|
+
if (v === null || typeof v !== "object") return v;
|
|
2026
|
+
if (depth >= MAX_DEPTH) return "[too deep]";
|
|
2027
|
+
if (Array.isArray(v)) return v.map((x) => walk(x, key, depth + 1, secret));
|
|
2028
|
+
const out = /* @__PURE__ */ Object.create(null);
|
|
2029
|
+
for (const [k, x] of Object.entries(v)) {
|
|
2030
|
+
const name = scrubKey(k, key);
|
|
2031
|
+
const hidden = unhide(k);
|
|
2032
|
+
const secret2 = isSecretName(hidden) || SECRET_HEADERS.has(hidden.toLowerCase());
|
|
2033
|
+
out[name] = walk(x, key, depth + 1, secret2);
|
|
2034
|
+
}
|
|
2035
|
+
return out;
|
|
2036
|
+
}
|
|
2037
|
+
function cap(text, json) {
|
|
2038
|
+
return text.length > EXCHANGE_BODY_MAX ? { text: text.slice(0, EXCHANGE_BODY_MAX), json, bytes: text.length, truncated: true } : { text, json, bytes: text.length, truncated: false };
|
|
2039
|
+
}
|
|
2040
|
+
function captureBody(value, key) {
|
|
2041
|
+
if (value === void 0) return { text: "", json: false, bytes: 0, truncated: false };
|
|
2042
|
+
return cap(JSON.stringify(walk(value, key, 0, false)) ?? "", true);
|
|
2043
|
+
}
|
|
2044
|
+
function captureText(text, key, parsed) {
|
|
2045
|
+
if (text.length > SCRUB_WINDOW) {
|
|
2046
|
+
return {
|
|
2047
|
+
text: scrubHead(text, SCRUB_WINDOW, key).slice(0, EXCHANGE_BODY_MAX),
|
|
2048
|
+
json: false,
|
|
2049
|
+
bytes: text.length,
|
|
2050
|
+
truncated: true
|
|
2051
|
+
};
|
|
2052
|
+
}
|
|
2053
|
+
if (parsed !== void 0) return captureBody(parsed, key);
|
|
2054
|
+
let value;
|
|
2055
|
+
try {
|
|
2056
|
+
value = JSON.parse(text);
|
|
2057
|
+
} catch {
|
|
2058
|
+
return cap(scrubKey(text, key), false);
|
|
2059
|
+
}
|
|
2060
|
+
return captureBody(value, key);
|
|
2061
|
+
}
|
|
2062
|
+
|
|
2063
|
+
// src/adjudicator/providers/http.ts
|
|
2064
|
+
var RETRY_MIN_REMAINING_MS = 300;
|
|
2065
|
+
var RETRY_BACKOFF_MS = 100;
|
|
2066
|
+
var BODY_SNIPPET = 500;
|
|
2067
|
+
var ProviderError = class extends Error {
|
|
2068
|
+
kind;
|
|
2069
|
+
/** HTTP status, when the server answered. */
|
|
2070
|
+
status;
|
|
2071
|
+
/** Scrubbed start of the response body, for http_error. */
|
|
2072
|
+
body;
|
|
2073
|
+
constructor(kind, message, o = {}) {
|
|
2074
|
+
super(message);
|
|
2075
|
+
this.name = "ProviderError";
|
|
2076
|
+
this.kind = kind;
|
|
2077
|
+
this.status = o.status;
|
|
2078
|
+
this.body = o.body;
|
|
2079
|
+
}
|
|
2080
|
+
};
|
|
2081
|
+
var PROBE_TIMEOUT_MS = 2e3;
|
|
2082
|
+
function scrub(text, key) {
|
|
2083
|
+
return scrubKey(text, key);
|
|
2084
|
+
}
|
|
2085
|
+
function postJson(url, body, o) {
|
|
2086
|
+
return request("POST", url, body, o);
|
|
2087
|
+
}
|
|
2088
|
+
function getJson(url, o) {
|
|
2089
|
+
return request("GET", url, void 0, o);
|
|
2090
|
+
}
|
|
2091
|
+
function joinUrl(base, path) {
|
|
2092
|
+
return `${base.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
|
|
2093
|
+
}
|
|
2094
|
+
async function request(method, url, body, o) {
|
|
2095
|
+
const now = o.now ?? Date.now;
|
|
2096
|
+
const doFetch = o.fetch ?? ((u, i) => globalThis.fetch(u, i));
|
|
2097
|
+
const start = now();
|
|
2098
|
+
const end = start + Math.max(0, o.deadlineMs);
|
|
2099
|
+
const target2 = safeTarget(url);
|
|
2100
|
+
const payload = method === "POST" ? JSON.stringify(body) : void 0;
|
|
2101
|
+
let x;
|
|
2102
|
+
let lastText;
|
|
2103
|
+
let lastStatus;
|
|
2104
|
+
const settle2 = (outcome, status, error) => {
|
|
2105
|
+
if (!x) return;
|
|
2106
|
+
try {
|
|
2107
|
+
x.durationMs = now() - x.sentAt;
|
|
2108
|
+
x.status = status;
|
|
2109
|
+
if (x.response === null && lastText !== void 0) x.response = captureText(lastText, o.key);
|
|
2110
|
+
if (error !== void 0) x.error = error;
|
|
2111
|
+
x.outcome = outcome;
|
|
2112
|
+
} catch {
|
|
2113
|
+
x.outcome = outcome;
|
|
2114
|
+
}
|
|
2115
|
+
};
|
|
2116
|
+
const fail = (e) => {
|
|
2117
|
+
if (x) settle2(e.kind, e.status ?? lastStatus ?? null, scrub(e.message, o.key));
|
|
2118
|
+
o.log?.(scrub(`adjudicator ${method} ${target2}: ${e.kind} ${e.message}`, o.key));
|
|
2119
|
+
throw e;
|
|
2120
|
+
};
|
|
2121
|
+
const headers = { accept: "application/json", ...o.headers };
|
|
2122
|
+
if (payload !== void 0) headers["content-type"] = "application/json";
|
|
2123
|
+
if (o.key) headers.authorization = `Bearer ${o.key}`;
|
|
2124
|
+
for (let attempt = 1; ; attempt++) {
|
|
2125
|
+
const remaining = end - now();
|
|
2126
|
+
if (remaining <= 0 || o.signal?.aborted)
|
|
2127
|
+
return fail(new ProviderError("timeout", `deadline of ${o.deadlineMs} ms passed`));
|
|
2128
|
+
const ctl = new AbortController();
|
|
2129
|
+
const timer = setTimeout(() => ctl.abort(), remaining);
|
|
2130
|
+
const onAbort = () => ctl.abort();
|
|
2131
|
+
o.signal?.addEventListener("abort", onAbort, { once: true });
|
|
2132
|
+
let res;
|
|
2133
|
+
let text;
|
|
2134
|
+
const attemptStart = now();
|
|
2135
|
+
try {
|
|
2136
|
+
const init = { method, headers, signal: ctl.signal, redirect: "manual" };
|
|
2137
|
+
if (payload !== void 0) init.body = payload;
|
|
2138
|
+
const sent = doFetch(url, init);
|
|
2139
|
+
if (o.exchange && !x) {
|
|
2140
|
+
try {
|
|
2141
|
+
const rec = {
|
|
2142
|
+
method,
|
|
2143
|
+
target: target2,
|
|
2144
|
+
sentAt: attemptStart,
|
|
2145
|
+
deadlineMs: o.deadlineMs,
|
|
2146
|
+
attempts: 1,
|
|
2147
|
+
retryWaitMs: 0,
|
|
2148
|
+
ttfbMs: null,
|
|
2149
|
+
durationMs: null,
|
|
2150
|
+
status: null,
|
|
2151
|
+
outcome: "pending",
|
|
2152
|
+
request: captureBody(body, o.key),
|
|
2153
|
+
response: null,
|
|
2154
|
+
error: null
|
|
2155
|
+
};
|
|
2156
|
+
o.exchange(rec);
|
|
2157
|
+
x = rec;
|
|
2158
|
+
} catch {
|
|
2159
|
+
x = void 0;
|
|
2160
|
+
}
|
|
2161
|
+
}
|
|
2162
|
+
res = await sent;
|
|
2163
|
+
if (x) x.ttfbMs = now() - attemptStart;
|
|
2164
|
+
text = await res.text();
|
|
2165
|
+
lastText = text;
|
|
2166
|
+
lastStatus = res.status;
|
|
2167
|
+
} catch (err) {
|
|
2168
|
+
if (ctl.signal.aborted || o.signal?.aborted || isAbort(err))
|
|
2169
|
+
return fail(new ProviderError("timeout", `no answer within ${o.deadlineMs} ms`));
|
|
2170
|
+
return fail(new ProviderError("http_error", `request failed: ${scrub(errText(err), o.key)}`));
|
|
2171
|
+
} finally {
|
|
2172
|
+
clearTimeout(timer);
|
|
2173
|
+
o.signal?.removeEventListener("abort", onAbort);
|
|
2174
|
+
}
|
|
2175
|
+
if (res.status >= 300 && res.status < 400 || res.type === "opaqueredirect") {
|
|
2176
|
+
const to = res.headers.get("location");
|
|
2177
|
+
return fail(
|
|
2178
|
+
new ProviderError(
|
|
2179
|
+
"http_error",
|
|
2180
|
+
`HTTP ${res.status || "redirect"}: redirect${to ? ` to ${redirectTarget(to, url)}` : ""} refused; set the provider's base URL to the final endpoint`,
|
|
2181
|
+
{ status: res.status }
|
|
2182
|
+
)
|
|
2183
|
+
);
|
|
2184
|
+
}
|
|
2185
|
+
if (res.status === 429 || res.status === 529) {
|
|
2186
|
+
const wait = retryWait(res.headers.get("retry-after"));
|
|
2187
|
+
const left = end - now() - wait;
|
|
2188
|
+
if (o.retry !== false && attempt === 1 && left >= RETRY_MIN_REMAINING_MS) {
|
|
2189
|
+
o.log?.(`adjudicator ${method} ${target2}: ${res.status}, retrying in ${wait} ms`);
|
|
2190
|
+
if (x) {
|
|
2191
|
+
x.attempts = 2;
|
|
2192
|
+
x.retryWaitMs = wait;
|
|
2193
|
+
x.ttfbMs = null;
|
|
2194
|
+
}
|
|
2195
|
+
lastText = void 0;
|
|
2196
|
+
lastStatus = void 0;
|
|
2197
|
+
if (!await sleep(wait, o.signal))
|
|
2198
|
+
return fail(new ProviderError("timeout", "aborted while waiting to retry"));
|
|
2199
|
+
continue;
|
|
2200
|
+
}
|
|
2201
|
+
return fail(
|
|
2202
|
+
new ProviderError("rate_limited", `HTTP ${res.status}`, {
|
|
2203
|
+
status: res.status,
|
|
2204
|
+
body: scrubHead(text, BODY_SNIPPET, o.key)
|
|
2205
|
+
})
|
|
2206
|
+
);
|
|
2207
|
+
}
|
|
2208
|
+
if (res.status < 200 || res.status >= 300) {
|
|
2209
|
+
const snippet = scrubHead(text, BODY_SNIPPET, o.key);
|
|
2210
|
+
return fail(
|
|
2211
|
+
new ProviderError("http_error", `HTTP ${res.status}: ${snippet.slice(0, 160)}`, {
|
|
2212
|
+
status: res.status,
|
|
2213
|
+
body: snippet
|
|
2214
|
+
})
|
|
2215
|
+
);
|
|
2216
|
+
}
|
|
2217
|
+
let json;
|
|
2218
|
+
try {
|
|
2219
|
+
json = JSON.parse(text);
|
|
2220
|
+
} catch {
|
|
2221
|
+
return fail(new ProviderError("invalid", "the response is not JSON", { status: res.status }));
|
|
2222
|
+
}
|
|
2223
|
+
if (x) {
|
|
2224
|
+
try {
|
|
2225
|
+
x.response = captureText(text, o.key, json);
|
|
2226
|
+
} catch {
|
|
2227
|
+
x.response = null;
|
|
2228
|
+
}
|
|
2229
|
+
lastText = void 0;
|
|
2230
|
+
}
|
|
2231
|
+
settle2("ok", res.status);
|
|
2232
|
+
return { status: res.status, json, latencyMs: now() - start, attempts: attempt };
|
|
2233
|
+
}
|
|
2234
|
+
}
|
|
2235
|
+
function retryWait(header) {
|
|
2236
|
+
if (!header) return RETRY_BACKOFF_MS;
|
|
2237
|
+
const s = Number(header);
|
|
2238
|
+
if (Number.isFinite(s) && s >= 0) return Math.round(s * 1e3);
|
|
2239
|
+
const at = Date.parse(header);
|
|
2240
|
+
return Number.isFinite(at) ? Math.max(0, at - Date.now()) : RETRY_BACKOFF_MS;
|
|
2241
|
+
}
|
|
2242
|
+
function sleep(ms, signal) {
|
|
2243
|
+
return new Promise((resolve) => {
|
|
2244
|
+
if (signal?.aborted) return resolve(false);
|
|
2245
|
+
const t = setTimeout(() => {
|
|
2246
|
+
signal?.removeEventListener("abort", onAbort);
|
|
2247
|
+
resolve(true);
|
|
2248
|
+
}, ms);
|
|
2249
|
+
const onAbort = () => {
|
|
2250
|
+
clearTimeout(t);
|
|
2251
|
+
resolve(false);
|
|
2252
|
+
};
|
|
2253
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
2254
|
+
});
|
|
2255
|
+
}
|
|
2256
|
+
function isAbort(err) {
|
|
2257
|
+
return err instanceof Error && (err.name === "AbortError" || err.name === "TimeoutError");
|
|
2258
|
+
}
|
|
2259
|
+
function errText(err) {
|
|
2260
|
+
if (!(err instanceof Error)) return String(err);
|
|
2261
|
+
const cause = err.cause;
|
|
2262
|
+
const code = cause && typeof cause === "object" ? cause.code : void 0;
|
|
2263
|
+
return typeof code === "string" ? `${err.message} (${code})` : err.message;
|
|
2264
|
+
}
|
|
2265
|
+
function safeTarget(url) {
|
|
2266
|
+
try {
|
|
2267
|
+
const u = new URL(url);
|
|
2268
|
+
return `${u.host}${u.pathname}`;
|
|
2269
|
+
} catch {
|
|
2270
|
+
return "(bad url)";
|
|
2271
|
+
}
|
|
2272
|
+
}
|
|
2273
|
+
function redirectTarget(location, base) {
|
|
2274
|
+
try {
|
|
2275
|
+
return safeTarget(new URL(location, base).href);
|
|
2276
|
+
} catch {
|
|
2277
|
+
return "(bad url)";
|
|
2278
|
+
}
|
|
2279
|
+
}
|
|
2280
|
+
function probeDetail(err) {
|
|
2281
|
+
if (err instanceof ProviderError) return `${err.kind}: ${err.message}`;
|
|
2282
|
+
return scrub(err instanceof Error ? err.message : String(err));
|
|
2283
|
+
}
|
|
2284
|
+
|
|
2285
|
+
// src/adjudicator/providers/openai.ts
|
|
2286
|
+
var MAX_TOKENS_BASE = 64;
|
|
2287
|
+
var MAX_TOKENS_PER_ITEM = 48;
|
|
2288
|
+
function rejectsSchema(err) {
|
|
2289
|
+
return err instanceof ProviderError && err.status === 400 && /response_format|json_schema/i.test(`${err.body ?? ""} ${err.message}`);
|
|
2290
|
+
}
|
|
2291
|
+
var Completion = external_exports.object({
|
|
2292
|
+
model: external_exports.string().optional(),
|
|
2293
|
+
choices: external_exports.array(external_exports.object({ message: external_exports.object({ content: external_exports.string().nullable().optional() }) })).min(1),
|
|
2294
|
+
usage: external_exports.object({
|
|
2295
|
+
prompt_tokens: external_exports.number().nonnegative().optional(),
|
|
2296
|
+
completion_tokens: external_exports.number().nonnegative().optional()
|
|
2297
|
+
}).nullable().optional()
|
|
2298
|
+
});
|
|
2299
|
+
function parseContent(content) {
|
|
2300
|
+
const t = content.trim();
|
|
2301
|
+
const fenced = /^```(?:json)?\s*([\s\S]*?)\s*```$/i.exec(t);
|
|
2302
|
+
try {
|
|
2303
|
+
return JSON.parse(fenced?.[1] ?? t);
|
|
2304
|
+
} catch {
|
|
2305
|
+
throw new ProviderError("invalid", "the model's answer is not JSON");
|
|
2306
|
+
}
|
|
2307
|
+
}
|
|
2308
|
+
function itemSchema(cat, askTool) {
|
|
2309
|
+
const names = (xs) => xs.map((x) => x.name);
|
|
2310
|
+
return external_exports.object({
|
|
2311
|
+
id: external_exports.string(),
|
|
2312
|
+
complexity: external_exports.number().min(0).max(4),
|
|
2313
|
+
tier: external_exports.enum(names(cat.tiers)),
|
|
2314
|
+
role: external_exports.enum(names(cat.roles)),
|
|
2315
|
+
tool: askTool ? external_exports.enum(names(cat.tools)) : external_exports.unknown().optional(),
|
|
2316
|
+
risky: external_exports.boolean(),
|
|
2317
|
+
confidence: external_exports.number().min(0).max(1)
|
|
2318
|
+
});
|
|
2319
|
+
}
|
|
2320
|
+
function createOpenAIProvider(r, cfg, deps = {}) {
|
|
2321
|
+
const baseUrl = r.baseUrl;
|
|
2322
|
+
const askTool = (cfg.weights.features.toolProvider ?? 0) > 0;
|
|
2323
|
+
const json = cfg.openai.json;
|
|
2324
|
+
const price = r.local ? { inPerM: 0, outPerM: 0 } : r.price ?? null;
|
|
2325
|
+
let schemaRejected = false;
|
|
2326
|
+
const key = () => {
|
|
2327
|
+
if (!r.keyId) return void 0;
|
|
2328
|
+
const k = keyFor(r, deps.env)?.key;
|
|
2329
|
+
if (!k && r.remote) throw new ProviderError("no_key", `no key for ${r.keyId}`);
|
|
2330
|
+
return k;
|
|
2331
|
+
};
|
|
2332
|
+
function body(cat, items, schema, mode) {
|
|
2333
|
+
const b = {
|
|
2334
|
+
...r.extra,
|
|
2335
|
+
temperature: 0,
|
|
2336
|
+
max_tokens: MAX_TOKENS_BASE + MAX_TOKENS_PER_ITEM * items.length,
|
|
2337
|
+
messages: [
|
|
2338
|
+
{
|
|
2339
|
+
role: "system",
|
|
2340
|
+
content: rubric(cat, mode === "object" ? { askTool, schema } : { askTool })
|
|
2341
|
+
},
|
|
2342
|
+
{ role: "user", content: JSON.stringify({ items }) }
|
|
2343
|
+
],
|
|
2344
|
+
response_format: mode === "schema" ? { type: "json_schema", json_schema: { name: "adjudication", strict: true, schema } } : { type: "json_object" }
|
|
2345
|
+
};
|
|
2346
|
+
if (r.model) b.model = r.model;
|
|
2347
|
+
if (r.model && /gpt-oss/i.test(r.model) && b.reasoning_effort === void 0)
|
|
2348
|
+
b.reasoning_effort = "low";
|
|
2349
|
+
return b;
|
|
2350
|
+
}
|
|
2351
|
+
return {
|
|
2352
|
+
id: "openai-compatible",
|
|
2353
|
+
remote: r.remote,
|
|
2354
|
+
dims: askTool ? ["complexity", "tier", "role", "tool"] : ["complexity", "tier", "role"],
|
|
2355
|
+
async adjudicate(reqs, o) {
|
|
2356
|
+
if (reqs.length === 0)
|
|
2357
|
+
return {
|
|
2358
|
+
answers: [],
|
|
2359
|
+
usage: { inTok: 0, outTok: 0, costUsd: 0 },
|
|
2360
|
+
latencyMs: 0,
|
|
2361
|
+
model: r.model ?? ""
|
|
2362
|
+
};
|
|
2363
|
+
if (!baseUrl)
|
|
2364
|
+
throw new ProviderError("invalid", "no baseUrl for the openai-compatible provider");
|
|
2365
|
+
const now = deps.now ?? Date.now;
|
|
2366
|
+
const start = now();
|
|
2367
|
+
const cat = buildCatalogue(reqs, { tiers: cfg.tiers, tools: deps.tools });
|
|
2368
|
+
const items = buildItems(reqs, cfg.privacy.maxChars);
|
|
2369
|
+
const schema = openaiSchema(
|
|
2370
|
+
cat,
|
|
2371
|
+
items.map((i) => i.id),
|
|
2372
|
+
{ askTool }
|
|
2373
|
+
);
|
|
2374
|
+
const k = key();
|
|
2375
|
+
const send = (mode) => postJson(joinUrl(baseUrl, "chat/completions"), body(cat, items, schema, mode), {
|
|
2376
|
+
deadlineMs: o.deadlineMs - (now() - start),
|
|
2377
|
+
signal: o.signal,
|
|
2378
|
+
key: k,
|
|
2379
|
+
fetch: deps.fetch,
|
|
2380
|
+
now: deps.now,
|
|
2381
|
+
log: deps.log
|
|
2382
|
+
});
|
|
2383
|
+
let res;
|
|
2384
|
+
const first = json === "object" || json === "auto" && schemaRejected ? "object" : "schema";
|
|
2385
|
+
try {
|
|
2386
|
+
res = await send(first);
|
|
2387
|
+
} catch (err) {
|
|
2388
|
+
if (first !== "schema" || json !== "auto" || !rejectsSchema(err)) throw err;
|
|
2389
|
+
schemaRejected = true;
|
|
2390
|
+
deps.log?.("adjudicator openai: json_schema rejected, retrying with json_object");
|
|
2391
|
+
res = await send("object");
|
|
2392
|
+
}
|
|
2393
|
+
const parsed = Completion.safeParse(res.json);
|
|
2394
|
+
if (!parsed.success)
|
|
2395
|
+
throw new ProviderError("invalid", "the completion has an unexpected shape");
|
|
2396
|
+
const content = parsed.data.choices[0]?.message.content;
|
|
2397
|
+
if (!content) throw new ProviderError("invalid", "the completion has no content");
|
|
2398
|
+
const answers = mapAnswers(reqs, items, parseContent(content), cat, askTool);
|
|
2399
|
+
if (answers.length === 0) throw new ProviderError("invalid", "the answer matched no item");
|
|
2400
|
+
const inTok = parsed.data.usage?.prompt_tokens ?? 0;
|
|
2401
|
+
const outTok = parsed.data.usage?.completion_tokens ?? 0;
|
|
2402
|
+
return {
|
|
2403
|
+
answers,
|
|
2404
|
+
// An unknown price (cerebras, custom without openai.price) is null, not 0: the spend cap
|
|
2405
|
+
// and stats tell "unknown" apart from "free".
|
|
2406
|
+
usage: {
|
|
2407
|
+
inTok,
|
|
2408
|
+
outTok,
|
|
2409
|
+
costUsd: price ? (inTok * price.inPerM + outTok * price.outPerM) / 1e6 : null
|
|
2410
|
+
},
|
|
2411
|
+
latencyMs: now() - start,
|
|
2412
|
+
model: parsed.data.model ?? r.model ?? ""
|
|
2413
|
+
};
|
|
2414
|
+
},
|
|
2415
|
+
async probe(o) {
|
|
2416
|
+
const t0 = (deps.now ?? Date.now)();
|
|
2417
|
+
try {
|
|
2418
|
+
if (!baseUrl) throw new ProviderError("invalid", "no baseUrl");
|
|
2419
|
+
const res = await getJson(joinUrl(baseUrl, "models"), {
|
|
2420
|
+
deadlineMs: PROBE_TIMEOUT_MS,
|
|
2421
|
+
signal: o.signal,
|
|
2422
|
+
key: key(),
|
|
2423
|
+
fetch: deps.fetch,
|
|
2424
|
+
now: deps.now,
|
|
2425
|
+
retry: false
|
|
2426
|
+
});
|
|
2427
|
+
const ids = Array.isArray(res.json?.data) ? res.json.data.flatMap((m) => m.id ? [m.id] : []) : [];
|
|
2428
|
+
const detail = !r.model ? `reachable${ids[0] ? `, serving ${ids[0]}` : ""}` : ids.length === 0 || ids.includes(r.model) ? `${r.model} reachable` : `reachable, but ${r.model} is not listed`;
|
|
2429
|
+
return { ok: true, latencyMs: res.latencyMs, detail };
|
|
2430
|
+
} catch (err) {
|
|
2431
|
+
return { ok: false, latencyMs: (deps.now ?? Date.now)() - t0, detail: probeDetail(err) };
|
|
2432
|
+
}
|
|
2433
|
+
}
|
|
2434
|
+
};
|
|
2435
|
+
}
|
|
2436
|
+
function mapAnswers(reqs, items, raw, cat, askTool) {
|
|
2437
|
+
const list = raw && typeof raw === "object" && Array.isArray(raw.items) ? raw.items : void 0;
|
|
2438
|
+
if (!list) throw new ProviderError("invalid", "the answer has no items array");
|
|
2439
|
+
const Item = itemSchema(cat, askTool);
|
|
2440
|
+
const byId = /* @__PURE__ */ new Map();
|
|
2441
|
+
for (const x of list) {
|
|
2442
|
+
const p2 = Item.safeParse(x);
|
|
2443
|
+
if (p2.success && !byId.has(p2.data.id)) byId.set(p2.data.id, p2.data);
|
|
2444
|
+
}
|
|
2445
|
+
const out = [];
|
|
2446
|
+
reqs.forEach((req, i) => {
|
|
2447
|
+
const id = items[i]?.id;
|
|
2448
|
+
const x = id ? byId.get(id) : void 0;
|
|
2449
|
+
if (!x) return;
|
|
2450
|
+
const conf = x.confidence;
|
|
2451
|
+
const a = {
|
|
2452
|
+
key: req.key,
|
|
2453
|
+
complexity: { value: Math.round(x.complexity), confidence: conf },
|
|
2454
|
+
tier: { probs: { [x.tier]: 1 }, confidence: conf, calibrated: false },
|
|
2455
|
+
role: { probs: { [x.role]: 1 }, confidence: conf, calibrated: false },
|
|
2456
|
+
risky: x.risky ? 1 : 0
|
|
2457
|
+
};
|
|
2458
|
+
if (askTool && typeof x.tool === "string")
|
|
2459
|
+
a.tool = { probs: { [x.tool]: 1 }, confidence: conf, calibrated: false };
|
|
2460
|
+
out.push(a);
|
|
2461
|
+
});
|
|
2462
|
+
return out;
|
|
2463
|
+
}
|
|
2464
|
+
|
|
2465
|
+
// src/adjudicator/providers/typesafe.ts
|
|
2466
|
+
init_esm_shims();
|
|
2467
|
+
var JEV_PINNED = "jev-1.14.0";
|
|
2468
|
+
var JEV_DEFAULT_BASE_URL = "https://api.typesafe.ai/v1";
|
|
2469
|
+
var JEV_PRICE_IN = 0.042 / 1e6;
|
|
2470
|
+
var JEV_BATCH = 10;
|
|
2471
|
+
var JEV_MAX_REQUESTS = 2;
|
|
2472
|
+
function pinJevModel(model) {
|
|
2473
|
+
if (!model || /^jev-(latest|preview)$/i.test(model.trim())) return JEV_PINNED;
|
|
2474
|
+
return model.trim();
|
|
2475
|
+
}
|
|
2476
|
+
var Probabilities = external_exports.record(external_exports.string(), external_exports.number().min(0).max(1));
|
|
2477
|
+
var Answer = external_exports.union([
|
|
2478
|
+
external_exports.object({
|
|
2479
|
+
type: external_exports.literal("choice"),
|
|
2480
|
+
choice: external_exports.string(),
|
|
2481
|
+
confidence: external_exports.number().min(0).max(1).optional(),
|
|
2482
|
+
probabilities: Probabilities.optional()
|
|
2483
|
+
}),
|
|
2484
|
+
external_exports.object({
|
|
2485
|
+
type: external_exports.literal("score"),
|
|
2486
|
+
// On the rubric's own levels (0..criteria-1), echoed in `legend`; not 0..1.
|
|
2487
|
+
score: external_exports.number().min(0),
|
|
2488
|
+
confidence: external_exports.number().min(0).max(1).optional(),
|
|
2489
|
+
probabilities: Probabilities.optional(),
|
|
2490
|
+
legend: external_exports.record(external_exports.string(), external_exports.string()).optional()
|
|
2491
|
+
}),
|
|
2492
|
+
external_exports.object({ type: external_exports.literal("noul"), noul: external_exports.number().min(0).max(1) })
|
|
2493
|
+
]);
|
|
2494
|
+
var JevResponse = external_exports.object({
|
|
2495
|
+
model: external_exports.string().optional(),
|
|
2496
|
+
answers: external_exports.record(external_exports.string(), external_exports.unknown()),
|
|
2497
|
+
usage: external_exports.object({
|
|
2498
|
+
input_tokens: external_exports.number().nonnegative().optional(),
|
|
2499
|
+
output_tokens: external_exports.number().nonnegative().optional()
|
|
2500
|
+
}).optional()
|
|
2501
|
+
});
|
|
2502
|
+
function createTypesafeProvider(r, cfg, deps = {}) {
|
|
2503
|
+
const baseUrl = r.baseUrl ?? JEV_DEFAULT_BASE_URL;
|
|
2504
|
+
const model = pinJevModel(r.model);
|
|
2505
|
+
const askTool = (cfg.weights.features.toolProvider ?? 0) > 0;
|
|
2506
|
+
const key = () => {
|
|
2507
|
+
const k = keyFor({ ...r, keyId: r.keyId ?? "typesafe" }, deps.env)?.key;
|
|
2508
|
+
if (!k) throw new ProviderError("no_key", "no key for typesafe");
|
|
2509
|
+
return k;
|
|
2510
|
+
};
|
|
2511
|
+
async function one(reqs, offset, cat, o) {
|
|
2512
|
+
const items = buildItems(reqs, cfg.privacy.maxChars, offset);
|
|
2513
|
+
const k = key();
|
|
2514
|
+
let pe;
|
|
2515
|
+
const sink = o.capture;
|
|
2516
|
+
const exchange = sink ? (x) => {
|
|
2517
|
+
pe = {
|
|
2518
|
+
http: x,
|
|
2519
|
+
provider: "typesafe",
|
|
2520
|
+
model,
|
|
2521
|
+
batch: Math.floor(offset / JEV_BATCH),
|
|
2522
|
+
itemKeys: reqs.map((r3) => r3.key),
|
|
2523
|
+
tokensIn: null,
|
|
2524
|
+
tokensOut: null,
|
|
2525
|
+
costUsd: null
|
|
2526
|
+
};
|
|
2527
|
+
sink.push(pe);
|
|
2528
|
+
} : void 0;
|
|
2529
|
+
const invalid = (msg) => {
|
|
2530
|
+
if (pe) {
|
|
2531
|
+
pe.http.outcome = "invalid";
|
|
2532
|
+
pe.http.error = msg;
|
|
2533
|
+
}
|
|
2534
|
+
return new ProviderError("invalid", msg);
|
|
2535
|
+
};
|
|
2536
|
+
const res = await postJson(
|
|
2537
|
+
joinUrl(baseUrl, "systemone"),
|
|
2538
|
+
{ model, state: jevState(cat, items), questions: jevQuestions(items, cat, { askTool }) },
|
|
2539
|
+
{
|
|
2540
|
+
deadlineMs: o.deadlineMs,
|
|
2541
|
+
signal: o.signal,
|
|
2542
|
+
key: k,
|
|
2543
|
+
fetch: deps.fetch,
|
|
2544
|
+
now: deps.now,
|
|
2545
|
+
log: deps.log,
|
|
2546
|
+
exchange
|
|
2547
|
+
}
|
|
2548
|
+
);
|
|
2549
|
+
const parsed = JevResponse.safeParse(res.json);
|
|
2550
|
+
if (!parsed.success) throw invalid("the Jev response has an unexpected shape");
|
|
2551
|
+
const answers = mapAnswers2(reqs, items, parsed.data.answers, cat);
|
|
2552
|
+
if (answers.length === 0) throw invalid("the Jev response answered no item");
|
|
2553
|
+
const inTok = parsed.data.usage?.input_tokens ?? 0;
|
|
2554
|
+
if (pe) {
|
|
2555
|
+
pe.tokensIn = inTok;
|
|
2556
|
+
pe.tokensOut = parsed.data.usage?.output_tokens ?? 0;
|
|
2557
|
+
pe.costUsd = inTok * JEV_PRICE_IN;
|
|
2558
|
+
}
|
|
2559
|
+
return {
|
|
2560
|
+
answers,
|
|
2561
|
+
inTok,
|
|
2562
|
+
outTok: parsed.data.usage?.output_tokens ?? 0,
|
|
2563
|
+
latencyMs: res.latencyMs,
|
|
2564
|
+
model: parsed.data.model ?? model
|
|
2565
|
+
};
|
|
2566
|
+
}
|
|
2567
|
+
return {
|
|
2568
|
+
id: "typesafe",
|
|
2569
|
+
remote: r.remote,
|
|
2570
|
+
dims: askTool ? ["complexity", "tier", "role", "tool"] : ["complexity", "tier", "role"],
|
|
2571
|
+
async adjudicate(reqs, o) {
|
|
2572
|
+
if (reqs.length === 0)
|
|
2573
|
+
return { answers: [], usage: { inTok: 0, outTok: 0, costUsd: 0 }, latencyMs: 0, model };
|
|
2574
|
+
const cat = buildCatalogue(reqs, { tiers: cfg.tiers, tools: deps.tools });
|
|
2575
|
+
const chunks = [];
|
|
2576
|
+
for (let i = 0; i < reqs.length && chunks.length < JEV_MAX_REQUESTS; i += JEV_BATCH)
|
|
2577
|
+
chunks.push({ reqs: reqs.slice(i, i + JEV_BATCH), offset: i });
|
|
2578
|
+
const settled = await Promise.allSettled(chunks.map((c) => one(c.reqs, c.offset, cat, o)));
|
|
2579
|
+
const ok = settled.flatMap((s) => s.status === "fulfilled" ? [s.value] : []);
|
|
2580
|
+
if (ok.length === 0) {
|
|
2581
|
+
const first = settled.find((s) => s.status === "rejected");
|
|
2582
|
+
throw first && first.status === "rejected" ? first.reason : new ProviderError("invalid", "no answer");
|
|
2583
|
+
}
|
|
2584
|
+
const inTok = ok.reduce((n, x) => n + x.inTok, 0);
|
|
2585
|
+
return {
|
|
2586
|
+
answers: ok.flatMap((x) => x.answers),
|
|
2587
|
+
usage: {
|
|
2588
|
+
inTok,
|
|
2589
|
+
outTok: ok.reduce((n, x) => n + x.outTok, 0),
|
|
2590
|
+
costUsd: inTok * JEV_PRICE_IN
|
|
2591
|
+
},
|
|
2592
|
+
latencyMs: Math.max(...ok.map((x) => x.latencyMs)),
|
|
2593
|
+
model: ok[0]?.model ?? model
|
|
2594
|
+
};
|
|
2595
|
+
},
|
|
2596
|
+
async probe(o) {
|
|
2597
|
+
const t0 = (deps.now ?? Date.now)();
|
|
2598
|
+
try {
|
|
2599
|
+
const res = await getJson(joinUrl(baseUrl, "models"), {
|
|
2600
|
+
deadlineMs: PROBE_TIMEOUT_MS,
|
|
2601
|
+
signal: o.signal,
|
|
2602
|
+
key: key(),
|
|
2603
|
+
fetch: deps.fetch,
|
|
2604
|
+
now: deps.now,
|
|
2605
|
+
retry: false
|
|
2606
|
+
});
|
|
2607
|
+
const ids = Array.isArray(res.json?.data) ? res.json.data.map((m) => m.id) : [];
|
|
2608
|
+
const listed = ids.length === 0 || ids.includes(model);
|
|
2609
|
+
return {
|
|
2610
|
+
ok: true,
|
|
2611
|
+
latencyMs: res.latencyMs,
|
|
2612
|
+
detail: listed ? `typesafe ${model} reachable` : `typesafe reachable, but ${model} is not listed`
|
|
2613
|
+
};
|
|
2614
|
+
} catch (err) {
|
|
2615
|
+
return { ok: false, latencyMs: (deps.now ?? Date.now)() - t0, detail: probeDetail(err) };
|
|
2616
|
+
}
|
|
2617
|
+
}
|
|
2618
|
+
};
|
|
2619
|
+
}
|
|
2620
|
+
function mapAnswers2(reqs, items, raw, cat) {
|
|
2621
|
+
const get = (id) => {
|
|
2622
|
+
const p2 = Answer.safeParse(raw[id]);
|
|
2623
|
+
return p2.success ? p2.data : void 0;
|
|
2624
|
+
};
|
|
2625
|
+
const out = [];
|
|
2626
|
+
reqs.forEach((req, i) => {
|
|
2627
|
+
const item = items[i];
|
|
2628
|
+
if (!item) return;
|
|
2629
|
+
const a = { key: req.key };
|
|
2630
|
+
const c = complexityOf(get(jevQuestionId(item.id, "complexity")));
|
|
2631
|
+
if (c) a.complexity = c;
|
|
2632
|
+
const tier = choiceDist(
|
|
2633
|
+
get(jevQuestionId(item.id, "tier")),
|
|
2634
|
+
cat.tiers.map((t) => t.name)
|
|
2635
|
+
);
|
|
2636
|
+
if (tier) a.tier = tier;
|
|
2637
|
+
const role = choiceDist(
|
|
2638
|
+
get(jevQuestionId(item.id, "role")),
|
|
2639
|
+
cat.roles.map((x) => x.name)
|
|
2640
|
+
);
|
|
2641
|
+
if (role) a.role = role;
|
|
2642
|
+
const tool = choiceDist(
|
|
2643
|
+
get(jevQuestionId(item.id, "tool")),
|
|
2644
|
+
cat.tools.map((t) => t.name)
|
|
2645
|
+
);
|
|
2646
|
+
if (tool) a.tool = tool;
|
|
2647
|
+
const risky = get(jevQuestionId(item.id, "risky"));
|
|
2648
|
+
if (risky?.type === "noul") a.risky = risky.noul;
|
|
2649
|
+
const plan = get(jevQuestionId(item.id, "needs_plan"));
|
|
2650
|
+
if (plan?.type === "noul") a.needsPlan = plan.noul;
|
|
2651
|
+
if (a.complexity || a.tier || a.role) out.push(a);
|
|
2652
|
+
});
|
|
2653
|
+
return out;
|
|
2654
|
+
}
|
|
2655
|
+
function complexityOf(a) {
|
|
2656
|
+
if (a?.type !== "score") return void 0;
|
|
2657
|
+
const levels = Object.keys(a.legend ?? {}).map(Number).filter(Number.isInteger);
|
|
2658
|
+
const top = levels.length > 1 ? Math.max(...levels) : COMPLEXITY_CRITERIA.length - 1;
|
|
2659
|
+
if (top <= 0 || a.score > top) return void 0;
|
|
2660
|
+
return { value: a.score * 4 / top, confidence: a.confidence ?? 0.5 };
|
|
2661
|
+
}
|
|
2662
|
+
function choiceDist(a, options) {
|
|
2663
|
+
if (a?.type !== "choice" || !options.includes(a.choice)) return void 0;
|
|
2664
|
+
const probs = {};
|
|
2665
|
+
let sum = 0;
|
|
2666
|
+
for (const o of options) {
|
|
2667
|
+
const p2 = a.probabilities?.[o];
|
|
2668
|
+
if (p2 !== void 0 && p2 > 0) {
|
|
2669
|
+
probs[o] = p2;
|
|
2670
|
+
sum += p2;
|
|
2671
|
+
}
|
|
2672
|
+
}
|
|
2673
|
+
if (sum <= 0)
|
|
2674
|
+
return { probs: { [a.choice]: 1 }, confidence: a.confidence ?? 0.5, calibrated: true };
|
|
2675
|
+
for (const o of Object.keys(probs)) probs[o] = (probs[o] ?? 0) / sum;
|
|
2676
|
+
return { probs, confidence: a.confidence ?? probs[a.choice] ?? 0.5, calibrated: true };
|
|
2677
|
+
}
|
|
2678
|
+
|
|
2679
|
+
// src/adjudicator/providers/index.ts
|
|
2680
|
+
var factories = /* @__PURE__ */ new Map([
|
|
2681
|
+
["typesafe", createTypesafeProvider],
|
|
2682
|
+
["openai-compatible", createOpenAIProvider]
|
|
2683
|
+
]);
|
|
2684
|
+
function createProvider(r, cfg, deps = {}) {
|
|
2685
|
+
if (r.provider === "heuristic") return null;
|
|
2686
|
+
const factory = factories.get(r.provider);
|
|
2687
|
+
return factory ? factory(r, cfg, deps) : null;
|
|
2688
|
+
}
|
|
2689
|
+
|
|
2690
|
+
// src/adjudicator/units.ts
|
|
2691
|
+
init_esm_shims();
|
|
2692
|
+
var UNIT_SURFACES = ["plan", "agent"];
|
|
2693
|
+
var UNIT_MAX = 40;
|
|
2694
|
+
var UNIT_CHARS = 4e3;
|
|
2695
|
+
var SATURATED_AT = 4;
|
|
2696
|
+
var UNITS_VERSION = "4";
|
|
2697
|
+
function unitScored(surface) {
|
|
2698
|
+
return UNIT_SURFACES.includes(surface);
|
|
2699
|
+
}
|
|
2700
|
+
var FENCE2 = /```[\s\S]*?(?:```|$)/g;
|
|
2701
|
+
var TRACE_LINE = /^\s*(?:at\s.+:\d+(?::\d+)?\)?\s*$|at\s[\w$.<>[\] ]+\s\(.*\)\s*$|(?:[A-Z]\w*)?(?:Error|Exception)(?::|\s+\[)|File ".+", line \d+|Traceback \(most recent call last\)|(?:FAIL|PASS|ERR!|×|✗|✓|✔)\s|npm (?:ERR|WARN)|\[?\d{4}-\d\d-\d\d[T ]\d\d:\d\d)/;
|
|
2702
|
+
var MARKER = /^\s*(?:(?:[-*+•>]|#{1,6}|\d{1,3}[.)]|\[[ xX]\])\s+)*(?:\*\*|__)?/;
|
|
2703
|
+
var SENTENCE_END = /(?<=[.!?]|[.!?]["'”’)\]])\s+(?=\S)/;
|
|
2704
|
+
var ABBREV = /(?:^|[\s(])(?:e\.g|i\.e|vs|cf|approx|incl|esp|resp|viz|al|fig|eq|ref|sec|vol|ca)\.$/i;
|
|
2705
|
+
var WORDY = /[\p{L}\p{N}]/u;
|
|
2706
|
+
function statements(text) {
|
|
2707
|
+
const s = text.slice(0, UNIT_CHARS);
|
|
2708
|
+
const out = [];
|
|
2709
|
+
let at = 0;
|
|
2710
|
+
for (const m of s.matchAll(FENCE2)) {
|
|
2711
|
+
const i = m.index ?? 0;
|
|
2712
|
+
prose(s.slice(at, i), out);
|
|
2713
|
+
pasted(m[0].trim(), out);
|
|
2714
|
+
at = i + m[0].length;
|
|
2715
|
+
}
|
|
2716
|
+
prose(s.slice(at), out);
|
|
2717
|
+
return out;
|
|
2718
|
+
}
|
|
2719
|
+
function pasted(block, out) {
|
|
2720
|
+
if (!WORDY.test(block)) return;
|
|
2721
|
+
const last = out.length - 1;
|
|
2722
|
+
if (last >= 0) out[last] = `${out[last]}
|
|
2723
|
+
${block}`;
|
|
2724
|
+
else out.push(block);
|
|
2725
|
+
}
|
|
2726
|
+
function prose(chunk, out) {
|
|
2727
|
+
let trace = [];
|
|
2728
|
+
const flush = () => {
|
|
2729
|
+
if (trace.length) pasted(trace.join("\n"), out);
|
|
2730
|
+
trace = [];
|
|
2731
|
+
};
|
|
2732
|
+
for (const line of chunk.split(/\r?\n/)) {
|
|
2733
|
+
if (TRACE_LINE.test(line)) {
|
|
2734
|
+
trace.push(line.trim());
|
|
2735
|
+
continue;
|
|
2736
|
+
}
|
|
2737
|
+
flush();
|
|
2738
|
+
const body = line.replace(MARKER, "").trim();
|
|
2739
|
+
if (!WORDY.test(body)) continue;
|
|
2740
|
+
let prev;
|
|
2741
|
+
for (const piece of body.split(SENTENCE_END)) {
|
|
2742
|
+
if (prev !== void 0 && ABBREV.test(prev)) {
|
|
2743
|
+
prev = `${prev} ${piece}`;
|
|
2744
|
+
continue;
|
|
2745
|
+
}
|
|
2746
|
+
if (prev !== void 0 && WORDY.test(prev)) out.push(prev);
|
|
2747
|
+
prev = piece;
|
|
2748
|
+
}
|
|
2749
|
+
if (prev !== void 0 && WORDY.test(prev)) out.push(prev);
|
|
2750
|
+
}
|
|
2751
|
+
flush();
|
|
2752
|
+
}
|
|
2753
|
+
var CLAUSE = /;\s+|,?\s+(?:and\s+)?then\s+/;
|
|
2754
|
+
var CLAUSE_MAX = 400;
|
|
2755
|
+
var LEAD_IN = /^(?:(?:then|finally|also|and|afterwards|after that|next|lastly|last|please|always|first|at the end|once (?:done|finished|it passes|that passes)|when (?:you(?:'re| are) )?(?:done|finished)|before (?:you )?(?:report(?:ing)?|finish(?:ing)?|clos(?:e|ing)|commit(?:ting)?|hand(?:ing)? (?:it )?(?:back|off)))[\s,:;-]+)+/;
|
|
2756
|
+
var TOOL_CHECK = String.raw`(?:npm (?:run [\w:-]+|test|t|ci)|npx (?:tsc|vitest|biome|jest|eslint|prettier|mocha|playwright)(?: run)?|(?:pnpm|yarn)(?: run)? [\w:-]+|type-?check\w*|tsc|lint\w*|biome|eslint|prettier|vitest|jest|pytest|go (?:test|vet)|cargo (?:test|check|clippy))`;
|
|
2757
|
+
var CHECK = `(?:${TOOL_CHECK}|tests?|specs?|test ?suites?|suites?|build|ci)`;
|
|
2758
|
+
var CHECK_SUFFIX = String.raw`(?: (?:--?[\w-]+(?:=\S+)?|[\w@+-]*[./*:][\w./:@+*=-]*|only|again|too|first|alone|there|here|as well)){0,4}?`;
|
|
2759
|
+
var CHECK_ITEM = String.raw`(?:(?:the|all|all the) )?(?:(?!(?:add|write|create|update|fix|cover|extend|implement|change|remove|delete|rewrite|refactor|make|keep|test)\b)[\w./:@+*-]+ ){0,2}?${CHECK}${CHECK_SUFFIX}`;
|
|
2760
|
+
var ROUTINE_ITEM = `(?:(?:the|all|full|whole|entire) ){0,3}(?:${TOOL_CHECK}|test ?suites?|tests?|specs?|suites?|build|ci)`;
|
|
2761
|
+
var AT_PATH = String.raw`(?: (?:in|under) [\w@+-]*[./*][\w./:@+*=-]+)?`;
|
|
2762
|
+
var REPORT_NOUN = "generation|generator|rows?|pages?|builder|templates?|exports?|service|views?|endpoints?|api|jobs?|schema|module|component|card|tab|screen|quer(?:y|ies)|model|pdf|csv|ui|dashboard";
|
|
2763
|
+
var PASSES = "(?:pass(?:es)?|be green|stay green|succeed|exits? 0|exit cleanly|(?:is|are) clean)";
|
|
2764
|
+
var IS_GREEN = "(?:is|are) (?:all )?(?:clean|green)";
|
|
2765
|
+
var LIST_SEP2 = String.raw`\s*(?:,|\+|&&?|/|\band\b|\bor\b)\s*`;
|
|
2766
|
+
var checkList = (item, verdict, sep = LIST_SEP2) => new RegExp(
|
|
2767
|
+
String.raw`^${item}(?:${sep}${item}){0,8}(?:\s+(?:must |should |all |still |both )*${verdict})?(?:\s+if [\w\s'-]{1,30})?\s*[.!]?$`
|
|
2768
|
+
);
|
|
2769
|
+
var CHECKS_ONLY = checkList(CHECK_ITEM, PASSES);
|
|
2770
|
+
var ROUTINE_CHECKS = checkList(
|
|
2771
|
+
`${ROUTINE_ITEM}${AT_PATH}${CHECK_SUFFIX}`,
|
|
2772
|
+
`(?:${PASSES}|${IS_GREEN})`,
|
|
2773
|
+
String.raw`\s*(?:,\s*(?:and|or)\b|,|\+|&&?|/|\band\b|\bor\b)\s*`
|
|
2774
|
+
);
|
|
2775
|
+
var ROUTINE_GREEN = checkList(`${ROUTINE_ITEM}${CHECK_SUFFIX}`, IS_GREEN);
|
|
2776
|
+
var CHECK_LABEL = /^(?:(?:verification|verify|validation|validate|confirm|acceptance(?: criteria)?|checks?|gates?|definition of done|done when|tests?)\s*:\s*|(?:done (?:when|once|if)|finished when)\s+)/;
|
|
2777
|
+
var LABEL_ONLY = /^(?:verification|verify|validation|validate|confirm|acceptance(?: criteria)?|checks?|gates?|tests?|definition of done|done when|hand-?in|deliverables?)\s*:\s*$/;
|
|
2778
|
+
var PROTOCOL = [
|
|
2779
|
+
// run the checks: "Run npm run typecheck, lint and the adjudicator tests"
|
|
2780
|
+
new RegExp(
|
|
2781
|
+
String.raw`^(?:re-?run|run|execute)\s+(?:(?:the|all|every|each|your|our|both|once|again|first|these|those|its|their)\s+)*(?:[\w./:@+*-]+\s+){0,3}?${CHECK}\b`
|
|
2782
|
+
),
|
|
2783
|
+
// the checks must pass: "Make sure typecheck and lint still pass"
|
|
2784
|
+
new RegExp(
|
|
2785
|
+
String.raw`^(?:verify|check|confirm|ensure|make sure|double-check|keep)\b.{0,80}?\b${CHECK}\b.{0,60}?\b(?:pass(?:es|ing)?|green|succeed\w*|clean|still (?:pass|work)\w*)\b`
|
|
2786
|
+
),
|
|
2787
|
+
// verify by the checks: "Verify with npm run build". The colon form ("Verify: npx tsc
|
|
2788
|
+
// --noEmit") goes through CHECK_LABEL, where every item must be a routine check (review2 R2-3).
|
|
2789
|
+
new RegExp(
|
|
2790
|
+
String.raw`^(?:verify|check|confirm|validate|test)\s+(?:it |this |them |that |everything )?(?:with|using|by running|via)\s+(?:the |a )?${CHECK}\b`
|
|
2791
|
+
),
|
|
2792
|
+
// reporting back: "Report the exact counts", "Report back (concise):"
|
|
2793
|
+
new RegExp(String.raw`^report\b(?!\s+(?:${REPORT_NOUN})\b)`),
|
|
2794
|
+
// closing the task: "Close the task with the counts", "End with a compact table"
|
|
2795
|
+
/^(?:close|finish|complete|wrap up|end)\s+(?:(?:the|this|your|each|every)\s+)?(?:tasks?|it|them|each|with|up|run)\b/,
|
|
2796
|
+
/^end (?:the |every |each |your |all )?(?:(?:pr|commit|reply|comment) )?(?:body|messages?|commits?|description|reply|comment|report)s? with\b/,
|
|
2797
|
+
/^(?:co-authored-by|🤖 generated with)\b/,
|
|
2798
|
+
// swarm protocol: "Claim it first: em_swarm {…}", "Mark it done with em_swarm update"; not a
|
|
2799
|
+
// claim with the work after it ("Claim it, read …, review …", "Claim: the PR now returns …")
|
|
2800
|
+
/^claim\b(?:[^:,]*|.*?[:(]\s*em_swarm\b.*)$/,
|
|
2801
|
+
/^(?:mark|update|use)\b.{0,80}?\bem_swarm\s*(?:\{|update\b)/,
|
|
2802
|
+
// handing results in: "Post the numbers to blackboard key X", "Put the counts in your update"
|
|
2803
|
+
// (a short object, then where it goes: "Record baseline numbers for … and post them" is work)
|
|
2804
|
+
/^(?:post|put|share|publish|include|give|state|list|record|log|leave|note|mention|summari[sz]e)\b(?:\s+\S+){0,12}?\s+(?:to|on|under|in|into|as|with|via|onto)\s+(?:the\s+|your\s+)?(?:blackboard|em_swarm|em_remember|(?:task\s+)?(?:update|result|results|report|final (?:message|report|answer)|reply|summary|hand-?off)\b|key\b)/,
|
|
2805
|
+
// the shape of the answer: "Return: a verdict, then findings", "Lead with the outcome"
|
|
2806
|
+
/^return(?:\s*:|\s+(?:in|as|only|at most|under|<=?)\s|\s+(?:(?:a|an|the|your)\s+)?(?:(?:dense|structured|short|concise|brief|compact|plain|one-line|final|markdown|json)\s+)*(?:report|summary|verdict|list|table|findings|results?|answer|overview)\b)/,
|
|
2807
|
+
/^lead with\b/,
|
|
2808
|
+
/^(?:test|check|lint|build|verify|verification) (?:commands?|steps?)\b/
|
|
2809
|
+
];
|
|
2810
|
+
var TAIL_LEAD = /(,?\s+and|\s+by|\s+after)\s+(?:then\s+)?([a-z][a-z-]*)\b/g;
|
|
2811
|
+
var WORK_VERBS = new Set(
|
|
2812
|
+
"fix patch redesign rewrite refactor migrate implement reimplement repair resolve debug investigate diagnose port find add update upgrade downgrade convert replace remove delete rename move extract split merge introduce create write change modify wire integrate harden optimize optimise restructure rework swap adopt bump configure enable disable extend document deprecate encrypt sanitize secure address correct design".split(" ")
|
|
2813
|
+
);
|
|
2814
|
+
var REPAIR_VERB = new Set(
|
|
2815
|
+
"fix fixing patch patching repair repairing resolve resolving debug debugging investigate investigating diagnose diagnosing find finding address addressing correct correcting".split(" ")
|
|
2816
|
+
);
|
|
2817
|
+
var NON_WORK_GERUND = /^(?:re-?running|running|executing|checking|re-?checking|verifying|confirming|ensuring|testing|re-?testing|linting|building|re-?building|type-?checking|reporting|posting|committing|pushing|claiming|closing|finishing|completing|marking|following|using|doing|making|keeping|passing|reading|looking|reviewing|editing|starting|getting|having|being|calling|invoking|merging|rebasing|pulling|fetching|installing|saving|switching)$/;
|
|
2818
|
+
var NOT_GERUND = /thing$|^(?:string|during|pending|morning|evening|ceiling|nothing|spring)$/;
|
|
2819
|
+
var ROUTINE_SUBJECT = "(?:it|they|you|tsc|eslint|biome|vitest|jest|lint|ci|the (?:linter|compiler|(?:type ?)?checker|tool|runner|build|ci|tsc|eslint|(?:(?:e2e|unit|integration|full|test) )?(?:tests?|specs?|suites?|test ?suites?)))";
|
|
2820
|
+
var REPORT_VERB = "(?:reports?|reported|finds?|found|prints?|shows?|says?|flags?|returns?|emits?|outputs?|surfaces?|raises?|raised|throws?|threw|fail(?:s|ed)?|breaks?)";
|
|
2821
|
+
var EMERGE = "(?:shows? up|comes? up|turns? up|pops? up|appears?|remains?|(?:is|are) (?:left|broken|failing|reported|flagged)|(?:get|gets|got) (?:reported|flagged)|(?:now|still) fail(?:s|ed)?|starts? failing|comes? back|you (?:see|find|hit)|needs? fixing|goes red|(?:doesn['\u2019]t|does not|don['\u2019]t|do not) pass)";
|
|
2822
|
+
var ROUTINE_OBJECT = new RegExp(
|
|
2823
|
+
String.raw`^(?:\s+(?:up|out))?(?:\s+(?:any|all|the|every|each|those|these|its|their|your|whatever|remaining|new|resulting|reported|other))*(?:\s+(?:type|types|lint|linter|typecheck|tsc|eslint|compile|compiler|compilation|build|ci|test|unit|vitest|jest|failing|failed|broken|outstanding)){0,3}\s+(?:failures?|failing|fails|errors?|issues?|problems?|regressions?|breakages?|findings?|warnings?|complaints?|tests?|specs?|checks?|them|it|those|these|that|anything|everything|what(?:ever)?\s+(?:${ROUTINE_SUBJECT}\s+${REPORT_VERB}|fail(?:s|ed)?|breaks?|broke|errors?|${EMERGE}))(?:\s+(?:if any|as needed|as necessary|you find|found|reported|first|again|too|along the way|before (?:reporting|you report|finishing|you finish|handing in|you hand in|you are done|you're done|moving on)|(?:(?:that|which) (?:${ROUTINE_SUBJECT} )?|${ROUTINE_SUBJECT} )${REPORT_VERB}|(?:that|which) ${EMERGE}))?\s*[.!]?$`
|
|
2824
|
+
);
|
|
2825
|
+
var HANDIN_OBJECT = /\b(?:em_swarm|em_remember|blackboard)\b|\b(?:to|in|into|on|under)\s+(?:the\s+|your\s+)?(?:task\s+)?(?:update|result|results|report|final (?:message|report|answer)|reply|summary|hand-?off)\s*[.!]?$/;
|
|
2826
|
+
function workTail(clause) {
|
|
2827
|
+
for (const m of clause.matchAll(TAIL_LEAD)) {
|
|
2828
|
+
const verb = m[2];
|
|
2829
|
+
const gerund = /^\w{3,}ing$/.test(verb) && !NON_WORK_GERUND.test(verb) && !NOT_GERUND.test(verb);
|
|
2830
|
+
const work = m[1].endsWith("and") ? WORK_VERBS.has(verb) : gerund;
|
|
2831
|
+
if (!work) continue;
|
|
2832
|
+
const rest = clause.slice((m.index ?? 0) + m[0].length);
|
|
2833
|
+
if (!/[\p{L}\p{N}]/u.test(rest)) continue;
|
|
2834
|
+
if (HANDIN_OBJECT.test(rest)) continue;
|
|
2835
|
+
if (REPAIR_VERB.has(verb) && ROUTINE_OBJECT.test(rest)) continue;
|
|
2836
|
+
return true;
|
|
2837
|
+
}
|
|
2838
|
+
return false;
|
|
2839
|
+
}
|
|
2840
|
+
function boilerplateClause(clause) {
|
|
2841
|
+
const lead = clause.toLowerCase().replace(/\*\*/g, "").replace(/^[\s("'“]+/, "").replace(LEAD_IN, "");
|
|
2842
|
+
const c = lead.replace(/`/g, "").trim();
|
|
2843
|
+
if (!c || c.length > CLAUSE_MAX) return false;
|
|
2844
|
+
if (LABEL_ONLY.test(c)) return "step";
|
|
2845
|
+
const rest = c.replace(CHECK_LABEL, "");
|
|
2846
|
+
if (rest !== c) return ROUTINE_CHECKS.test(rest) ? "label" : false;
|
|
2847
|
+
if (CHECKS_ONLY.test(c) || ROUTINE_GREEN.test(c)) return "step";
|
|
2848
|
+
return !lead.startsWith("`") && PROTOCOL.some((re) => re.test(c)) && !workTail(c) ? "step" : false;
|
|
2849
|
+
}
|
|
2850
|
+
var CONTINUATION = /^\s*(?:(?:the|a|an|any|all|each|every|its|their|your|our|this|these|those|which|what|whether|how|findings|results|counts|numbers|verdict|summary|list|table)\b|(?:\S+\s+)?\S+\s*[.!]?\s*$)/i;
|
|
2851
|
+
function trimBoilerplate(statement) {
|
|
2852
|
+
const clauses = statement.split(CLAUSE);
|
|
2853
|
+
const drop = [];
|
|
2854
|
+
let carry2 = false;
|
|
2855
|
+
for (const c of clauses) {
|
|
2856
|
+
const kind = boilerplateClause(c);
|
|
2857
|
+
const continued = !kind && carry2 && CONTINUATION.test(c);
|
|
2858
|
+
drop.push(!!kind || continued);
|
|
2859
|
+
carry2 = kind === "step" || continued;
|
|
2860
|
+
}
|
|
2861
|
+
const kept = clauses.filter((_, i) => !drop[i]);
|
|
2862
|
+
if (kept.length === clauses.length) return { kept: statement, removed: 0 };
|
|
2863
|
+
return { kept: kept.join("; ").trim(), removed: clauses.length - kept.length };
|
|
2864
|
+
}
|
|
2865
|
+
function splitUnits(text) {
|
|
2866
|
+
const units = [];
|
|
2867
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2868
|
+
let dropped = 0;
|
|
2869
|
+
for (const [i, s] of statements(text).entries()) {
|
|
2870
|
+
const t = trimBoilerplate(s);
|
|
2871
|
+
const unit = t.kept || (i === 0 ? s : "");
|
|
2872
|
+
if (unit !== s) dropped += t.removed;
|
|
2873
|
+
if (!unit) continue;
|
|
2874
|
+
const key = unit.toLowerCase().replace(/\s+/g, " ");
|
|
2875
|
+
if (seen.has(key)) continue;
|
|
2876
|
+
seen.add(key);
|
|
2877
|
+
units.push(unit);
|
|
2878
|
+
}
|
|
2879
|
+
if (units.length <= UNIT_MAX) return { units, dropped, skipped: 0, tail: [] };
|
|
2880
|
+
const tail = units.splice(UNIT_MAX - 1);
|
|
2881
|
+
units.push(tail.join("\n"));
|
|
2882
|
+
return { units, dropped, skipped: tail.length - 1, tail };
|
|
2883
|
+
}
|
|
2884
|
+
var EPS = 1e-9;
|
|
2885
|
+
var r23 = (n) => Math.round(n * 100) / 100;
|
|
2886
|
+
var sumOf = (reasons) => r23(reasons.reduce((s, r) => s + r.weight, 0));
|
|
2887
|
+
function scoreUnits(text, hints, weights = {}, catalogue = {}) {
|
|
2888
|
+
const { units: parts, dropped, skipped, tail } = splitUnits(text);
|
|
2889
|
+
if (parts.length <= 1 && dropped === 0 && statements(text).length <= 1) {
|
|
2890
|
+
const features2 = extractFeatures(text.replace(MARKER, ""), hints);
|
|
2891
|
+
const set2 = scoreHeuristic(features2, weights, catalogue);
|
|
2892
|
+
const sum2 = sumOf(set2.complexityReasons);
|
|
2893
|
+
return {
|
|
2894
|
+
set: set2,
|
|
2895
|
+
features: features2,
|
|
2896
|
+
units: briefOf(parts.length, 0, skipped, 0, sum2, set2.role.confidence)
|
|
2897
|
+
};
|
|
2898
|
+
}
|
|
2899
|
+
const features = extractFeatures(parts.join("\n"), hints);
|
|
2900
|
+
const w = { ...DEFAULT_FEATURE_WEIGHTS, ...weights.features ?? {} };
|
|
2901
|
+
const sum = sumOf(scoreComplexity(features, w).reasons);
|
|
2902
|
+
const risks = [];
|
|
2903
|
+
const score = (u) => {
|
|
2904
|
+
const f = extractFeatures(u, hints);
|
|
2905
|
+
risks.push(f.risk);
|
|
2906
|
+
return scoreHeuristic(f, weights, catalogue);
|
|
2907
|
+
};
|
|
2908
|
+
const scoredParts = tail.length ? [...parts.slice(0, -1), ...tail] : parts;
|
|
2909
|
+
const sets = scoredParts.map(score);
|
|
2910
|
+
const win = hardest(sets, risks);
|
|
2911
|
+
const top = sets[win];
|
|
2912
|
+
const role = sets.length === 1 ? top.role : summed(
|
|
2913
|
+
"role",
|
|
2914
|
+
sets.map((s) => s.role),
|
|
2915
|
+
roleOptions(catalogue),
|
|
2916
|
+
top.role
|
|
2917
|
+
);
|
|
2918
|
+
const tool = sets.length === 1 ? top.tool : summed(
|
|
2919
|
+
"tool",
|
|
2920
|
+
sets.map((s) => s.tool),
|
|
2921
|
+
toolOptions(catalogue),
|
|
2922
|
+
top.tool
|
|
2923
|
+
);
|
|
2924
|
+
const set = {
|
|
2925
|
+
complexity: top.complexity,
|
|
2926
|
+
complexityReasons: top.complexityReasons,
|
|
2927
|
+
tool,
|
|
2928
|
+
tier: top.tier,
|
|
2929
|
+
role,
|
|
2930
|
+
effort: top.effort
|
|
2931
|
+
};
|
|
2932
|
+
return {
|
|
2933
|
+
set,
|
|
2934
|
+
features,
|
|
2935
|
+
units: briefOf(sets.length, dropped, skipped, win, sum, role.confidence)
|
|
2936
|
+
};
|
|
2937
|
+
}
|
|
2938
|
+
function hardest(sets, risks) {
|
|
2939
|
+
let win = 0;
|
|
2940
|
+
for (let i = 1; i < sets.length; i++) {
|
|
2941
|
+
const c = sets[i].complexity;
|
|
2942
|
+
const best = sets[win].complexity;
|
|
2943
|
+
if (c > best + EPS || c > best - EPS && (risks[i] ?? 0) > (risks[win] ?? 0)) win = i;
|
|
2944
|
+
}
|
|
2945
|
+
return win;
|
|
2946
|
+
}
|
|
2947
|
+
function briefOf(scored, dropped, skipped, winner, sum, roleConfidence) {
|
|
2948
|
+
return {
|
|
2949
|
+
scored,
|
|
2950
|
+
dropped,
|
|
2951
|
+
skipped,
|
|
2952
|
+
winner,
|
|
2953
|
+
sum,
|
|
2954
|
+
saturated: sum > SATURATED_AT,
|
|
2955
|
+
roleConfidence: r23(roleConfidence)
|
|
2956
|
+
};
|
|
2957
|
+
}
|
|
2958
|
+
function roleOptions(catalogue) {
|
|
2959
|
+
return catalogue.roles?.length ? catalogue.roles.map((r) => r.name) : DEFAULT_ROLES;
|
|
2960
|
+
}
|
|
2961
|
+
function toolOptions(catalogue) {
|
|
2962
|
+
const tools = catalogue.tools ?? TOOL_IDS;
|
|
2963
|
+
return TOOL_IDS.filter((t) => tools.includes(t));
|
|
2964
|
+
}
|
|
2965
|
+
function summed(dimension, tables, options, fallback) {
|
|
2966
|
+
const votes = /* @__PURE__ */ new Map();
|
|
2967
|
+
let total = 0;
|
|
2968
|
+
for (const t of tables)
|
|
2969
|
+
for (const e of t.entries)
|
|
2970
|
+
for (const r of e.reasons) {
|
|
2971
|
+
if (r.feature === "fallback" || !(r.weight > 0)) continue;
|
|
2972
|
+
total += r.weight;
|
|
2973
|
+
let byReason = votes.get(e.option);
|
|
2974
|
+
if (!byReason) {
|
|
2975
|
+
byReason = /* @__PURE__ */ new Map();
|
|
2976
|
+
votes.set(e.option, byReason);
|
|
2977
|
+
}
|
|
2978
|
+
const k = `${r.feature}
|
|
2979
|
+
${r.note}`;
|
|
2980
|
+
const m = byReason.get(k);
|
|
2981
|
+
if (m) {
|
|
2982
|
+
m.weight += r.weight;
|
|
2983
|
+
m.n++;
|
|
2984
|
+
} else byReason.set(k, { ...r, n: 1 });
|
|
2985
|
+
}
|
|
2986
|
+
if (!(total > 0)) return fallback;
|
|
2987
|
+
const raw = /* @__PURE__ */ new Map();
|
|
2988
|
+
for (const [option, byReason] of votes) {
|
|
2989
|
+
let s = 0;
|
|
2990
|
+
const reasons = [];
|
|
2991
|
+
for (const m of byReason.values()) {
|
|
2992
|
+
s += m.weight;
|
|
2993
|
+
reasons.push({
|
|
2994
|
+
feature: m.feature,
|
|
2995
|
+
weight: r23(m.weight),
|
|
2996
|
+
note: m.n > 1 ? `${m.note} (${m.n} statements)` : m.note
|
|
2997
|
+
});
|
|
2998
|
+
}
|
|
2999
|
+
reasons.sort((a, b) => b.weight - a.weight);
|
|
3000
|
+
raw.set(option, { s, reasons });
|
|
3001
|
+
}
|
|
3002
|
+
return makeTable(dimension, options, raw, fallback.choice);
|
|
3003
|
+
}
|
|
3004
|
+
function briefUnitsOf(scores) {
|
|
3005
|
+
if (!scores) return void 0;
|
|
3006
|
+
try {
|
|
3007
|
+
const u = JSON.parse(scores)?.units;
|
|
3008
|
+
if (!u || typeof u !== "object") return void 0;
|
|
3009
|
+
const o = u;
|
|
3010
|
+
const n = (k) => {
|
|
3011
|
+
const v = o[k];
|
|
3012
|
+
return typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
3013
|
+
};
|
|
3014
|
+
const [scored, dropped, skipped, winner, sum, roleConfidence] = [
|
|
3015
|
+
n("scored"),
|
|
3016
|
+
n("dropped"),
|
|
3017
|
+
n("skipped"),
|
|
3018
|
+
n("winner"),
|
|
3019
|
+
n("sum"),
|
|
3020
|
+
n("roleConfidence")
|
|
3021
|
+
];
|
|
3022
|
+
if (scored === void 0 || dropped === void 0 || winner === void 0 || sum === void 0 || roleConfidence === void 0 || typeof o.saturated !== "boolean")
|
|
3023
|
+
return void 0;
|
|
3024
|
+
return {
|
|
3025
|
+
scored,
|
|
3026
|
+
dropped,
|
|
3027
|
+
skipped: skipped ?? 0,
|
|
3028
|
+
winner,
|
|
3029
|
+
sum,
|
|
3030
|
+
saturated: o.saturated,
|
|
3031
|
+
roleConfidence
|
|
3032
|
+
};
|
|
3033
|
+
} catch {
|
|
3034
|
+
return void 0;
|
|
3035
|
+
}
|
|
3036
|
+
}
|
|
3037
|
+
|
|
3038
|
+
// src/adjudicator/engine.ts
|
|
3039
|
+
var PENDING_TTL_MS = 3e4;
|
|
3040
|
+
var CACHE_KEY_CHARS = 2e3;
|
|
3041
|
+
var PRUNE_EVERY = 100;
|
|
3042
|
+
var LOCK_WAIT_MAX_MS = 250;
|
|
3043
|
+
var LOCK_WAIT_FLOOR_MS = 25;
|
|
3044
|
+
function surfaceBudget(cfg, surface, local = false) {
|
|
3045
|
+
const t = cfg.timeouts;
|
|
3046
|
+
switch (surface) {
|
|
3047
|
+
case "prompt":
|
|
3048
|
+
return local ? t.localMs : t.promptMs;
|
|
3049
|
+
case "agent":
|
|
3050
|
+
case "tool":
|
|
3051
|
+
return local ? t.localMs : t.agentMs;
|
|
3052
|
+
default:
|
|
3053
|
+
return t.planMs;
|
|
3054
|
+
}
|
|
3055
|
+
}
|
|
3056
|
+
function byUnits(surface, units) {
|
|
3057
|
+
return units !== false && unitScored(surface);
|
|
3058
|
+
}
|
|
3059
|
+
function stableJson(v) {
|
|
3060
|
+
if (Array.isArray(v)) return `[${v.map(stableJson).join(",")}]`;
|
|
3061
|
+
if (v && typeof v === "object") {
|
|
3062
|
+
const o = v;
|
|
3063
|
+
return `{${Object.keys(o).filter((k) => o[k] !== void 0).sort().map((k) => `${JSON.stringify(k)}:${stableJson(o[k])}`).join(",")}}`;
|
|
3064
|
+
}
|
|
3065
|
+
return JSON.stringify(v ?? null);
|
|
3066
|
+
}
|
|
3067
|
+
function cacheKeyFor(req, r, ctx = {}) {
|
|
3068
|
+
const text = normalize(req.text).toLowerCase().replace(/\s+/g, " ").slice(0, CACHE_KEY_CHARS);
|
|
3069
|
+
const h = req.hints;
|
|
3070
|
+
const roles = h?.roles ?? ctx.roles;
|
|
3071
|
+
const inputs = stableJson({
|
|
3072
|
+
files: h?.files,
|
|
3073
|
+
deps: h?.deps,
|
|
3074
|
+
role: h?.role,
|
|
3075
|
+
// Order does not change the scores; descriptions do (the heuristic matches on them).
|
|
3076
|
+
roles: roles?.map((x) => ({ name: x.name, description: x.description ?? "" })).sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0),
|
|
3077
|
+
// The docs names change the PKG rule (features.ts); a set, so order and repeats do not count.
|
|
3078
|
+
libraries: h?.libraries ? [...new Set(h.libraries)].sort() : void 0,
|
|
3079
|
+
tools: ctx.tools ? [...ctx.tools].sort() : void 0,
|
|
3080
|
+
// The ladder is ordered, so its order stays.
|
|
3081
|
+
tiers: ctx.config?.tiers,
|
|
3082
|
+
weights: ctx.config?.weights,
|
|
3083
|
+
// A brief scored per statement blends onto another heuristic than the same text scored whole.
|
|
3084
|
+
units: byUnits(req.surface, ctx.units) ? UNITS_VERSION : void 0
|
|
3085
|
+
});
|
|
3086
|
+
return createHash3("sha256").update([SCORER_VERSION, r.configured, r.model ?? "", req.surface, inputs, text].join("|")).digest("hex");
|
|
3087
|
+
}
|
|
3088
|
+
function toolsOf(opts) {
|
|
3089
|
+
return opts.tools ?? opts.deps?.tools;
|
|
3090
|
+
}
|
|
3091
|
+
function verdictColumns(v, o = {}) {
|
|
3092
|
+
const u = v.usage;
|
|
3093
|
+
const scores = {
|
|
3094
|
+
complexity: v.complexity,
|
|
3095
|
+
complexityReasons: v.complexityReasons,
|
|
3096
|
+
tool: v.tool,
|
|
3097
|
+
tier: v.tier,
|
|
3098
|
+
role: v.role,
|
|
3099
|
+
effort: v.effort,
|
|
3100
|
+
features: o.features,
|
|
3101
|
+
alpha: v.alpha,
|
|
3102
|
+
bias: v.bias,
|
|
3103
|
+
heuristicComplexity: v.heuristicComplexity,
|
|
3104
|
+
units: o.units
|
|
3105
|
+
};
|
|
3106
|
+
return {
|
|
3107
|
+
complexity: v.complexity,
|
|
3108
|
+
tier: v.tier.choice,
|
|
3109
|
+
effort: v.effort,
|
|
3110
|
+
tool: v.tool.choice,
|
|
3111
|
+
role: v.role.choice,
|
|
3112
|
+
confidence: v.tier.confidence,
|
|
3113
|
+
scores: JSON.stringify(scores),
|
|
3114
|
+
source: v.source,
|
|
3115
|
+
provider: v.provider,
|
|
3116
|
+
providerModel: o.providerModel ?? null,
|
|
3117
|
+
providerStatus: v.providerStatus,
|
|
3118
|
+
latencyMs: Math.round(v.latencyMs),
|
|
3119
|
+
providerLatencyMs: v.providerLatencyMs !== void 0 ? Math.round(v.providerLatencyMs) : null,
|
|
3120
|
+
tokensIn: u ? u.inTok : null,
|
|
3121
|
+
tokensOut: u ? u.outTok : null,
|
|
3122
|
+
costUsd: u ? u.costUsd : null,
|
|
3123
|
+
version: v.version
|
|
3124
|
+
};
|
|
3125
|
+
}
|
|
3126
|
+
var statusOfDegraded = (r) => {
|
|
3127
|
+
switch (r.degraded) {
|
|
3128
|
+
case "no_key":
|
|
3129
|
+
return "no_key";
|
|
3130
|
+
case "no_consent":
|
|
3131
|
+
return "no_consent";
|
|
3132
|
+
case void 0:
|
|
3133
|
+
return "n/a";
|
|
3134
|
+
default:
|
|
3135
|
+
return "skipped";
|
|
3136
|
+
}
|
|
3137
|
+
};
|
|
3138
|
+
async function adjudicate(reqs, opts) {
|
|
3139
|
+
const now = opts.now ?? Date.now;
|
|
3140
|
+
const start = now();
|
|
3141
|
+
const log = opts.log ?? (() => {
|
|
3142
|
+
});
|
|
3143
|
+
const items = [];
|
|
3144
|
+
try {
|
|
3145
|
+
const cfg = opts.config;
|
|
3146
|
+
for (const [index, req] of reqs.entries()) items.push(prepare(req, index, opts));
|
|
3147
|
+
if (!items.length) return [];
|
|
3148
|
+
const enabled = cfg.enabled || opts.ignoreEnabled === true;
|
|
3149
|
+
const r = opts.resolved ?? resolveAdjudicator(cfg, {
|
|
3150
|
+
context: opts.context ?? "hook",
|
|
3151
|
+
...opts.env ? { env: opts.env } : {}
|
|
3152
|
+
});
|
|
3153
|
+
const store = opts.store;
|
|
3154
|
+
const persist = !!store && (cfg.enabled ? opts.persist !== false : enabled && opts.persist === true);
|
|
3155
|
+
const firstSurface = items[0]?.req.surface ?? "prompt";
|
|
3156
|
+
const budgetMs = Math.max(0, opts.budgetMs ?? surfaceBudget(cfg, firstSurface, r.local));
|
|
3157
|
+
const locks = new LockBudget(start + budgetMs, now);
|
|
3158
|
+
const keyCtx = {
|
|
3159
|
+
config: cfg,
|
|
3160
|
+
tools: toolsOf(opts),
|
|
3161
|
+
roles: opts.roles,
|
|
3162
|
+
units: opts.units
|
|
3163
|
+
};
|
|
3164
|
+
for (const it of items) it.cacheKey = cacheKeyFor(it.req, r, keyCtx);
|
|
3165
|
+
if (!enabled) {
|
|
3166
|
+
for (const it of items) it.verdict = heuristicVerdict(it, "heuristic", "heuristic", "n/a");
|
|
3167
|
+
return finish(items, opts, start, now, false, locks);
|
|
3168
|
+
}
|
|
3169
|
+
const useCache = r.configured !== "heuristic" && !!store;
|
|
3170
|
+
if (useCache && store) {
|
|
3171
|
+
const ttlMs = cfg.cache.ttlHours * 36e5;
|
|
3172
|
+
for (const it of items) {
|
|
3173
|
+
const hit = safe(
|
|
3174
|
+
() => locks.run(store, 0, () => store.cacheGet(it.cacheKey, now(), ttlMs)),
|
|
3175
|
+
log
|
|
3176
|
+
);
|
|
3177
|
+
if (!hit) continue;
|
|
3178
|
+
if (hit.verdict) {
|
|
3179
|
+
const { id: _id, usage: _usage, providerLatencyMs: _pl, ...cached2 } = hit.verdict;
|
|
3180
|
+
it.verdict = { ...cached2, key: it.req.key, source: "cache" };
|
|
3181
|
+
} else if (now() - hit.createdAt < PENDING_TTL_MS) {
|
|
3182
|
+
it.pending = true;
|
|
3183
|
+
}
|
|
3184
|
+
}
|
|
3185
|
+
}
|
|
3186
|
+
const misses = items.filter((it) => !it.verdict);
|
|
3187
|
+
if (!misses.length) return finish(items, opts, start, now, persist, locks);
|
|
3188
|
+
const fallback = (status) => {
|
|
3189
|
+
const source = r.configured === "heuristic" ? "heuristic" : "fallback";
|
|
3190
|
+
for (const it of misses) it.verdict = heuristicVerdict(it, r.configured, source, status);
|
|
3191
|
+
return finish(items, opts, start, now, persist, locks);
|
|
3192
|
+
};
|
|
3193
|
+
if (r.provider === "heuristic") return fallback(statusOfDegraded(r));
|
|
3194
|
+
const call = opts.call ?? "inline";
|
|
3195
|
+
if (call === "never") return fallback("skipped");
|
|
3196
|
+
const provider = opts.provider !== void 0 ? opts.provider : safe(
|
|
3197
|
+
() => createProvider(r, cfg, { log, env: opts.env, tools: opts.tools, ...opts.deps }),
|
|
3198
|
+
log
|
|
3199
|
+
);
|
|
3200
|
+
if (!provider) return fallback("skipped");
|
|
3201
|
+
const keyFp = safe(() => currentKeyFingerprint(r, opts.env), log) ?? null;
|
|
3202
|
+
if (call === "async") {
|
|
3203
|
+
const peek = store ? safe(() => peekGate(store, r, cfg, now(), keyFp), log) : void 0;
|
|
3204
|
+
if (peek) return fallback(peek);
|
|
3205
|
+
for (const it of misses) {
|
|
3206
|
+
it.verdict = heuristicVerdict(it, r.configured, "fallback", "skipped");
|
|
3207
|
+
if (it.pending) it.queued = true;
|
|
3208
|
+
else it.job = buildJob(it, cfg, opts, budgetMs);
|
|
3209
|
+
}
|
|
3210
|
+
const out2 = finish(items, opts, start, now, persist, locks);
|
|
3211
|
+
for (const [i, it] of items.entries()) {
|
|
3212
|
+
if (!it.job) continue;
|
|
3213
|
+
const a = out2[i];
|
|
3214
|
+
if (a.rowId !== void 0) it.job.rowId = a.rowId;
|
|
3215
|
+
const job = it.job;
|
|
3216
|
+
const spawned = opts.spawn ? safe(() => opts.spawn?.(job), log) === true : false;
|
|
3217
|
+
if (!opts.spawn) log("adjudicator: async call without a spawn function");
|
|
3218
|
+
if (!spawned) continue;
|
|
3219
|
+
a.queued = true;
|
|
3220
|
+
if (store)
|
|
3221
|
+
safe(
|
|
3222
|
+
() => locks.run(store, 0, () => store.cachePut(it.cacheKey, null, r.configured, now())),
|
|
3223
|
+
log
|
|
3224
|
+
);
|
|
3225
|
+
}
|
|
3226
|
+
return out2;
|
|
3227
|
+
}
|
|
3228
|
+
let halfOpen = false;
|
|
3229
|
+
if (store) {
|
|
3230
|
+
const g = safe(
|
|
3231
|
+
() => locks.guard(
|
|
3232
|
+
() => gate(store, r.provider, cfg, {
|
|
3233
|
+
now: now(),
|
|
3234
|
+
remote: r.remote,
|
|
3235
|
+
keyFingerprint: keyFp,
|
|
3236
|
+
lockWaitMs: Math.floor(locks.wait() / 2)
|
|
3237
|
+
})
|
|
3238
|
+
),
|
|
3239
|
+
log
|
|
3240
|
+
);
|
|
3241
|
+
if (!g) {
|
|
3242
|
+
log(`adjudicator: ${r.provider} gate unavailable; heuristic this call`);
|
|
3243
|
+
return fallback("skipped");
|
|
3244
|
+
}
|
|
3245
|
+
if (!g.allow) return fallback(g.status);
|
|
3246
|
+
halfOpen = g.halfOpen;
|
|
3247
|
+
}
|
|
3248
|
+
const remaining = budgetMs - (now() - start);
|
|
3249
|
+
if (remaining <= 0) return fallback("timeout");
|
|
3250
|
+
const sink = cfg.privacy.captureExchanges === true && r.provider === "typesafe" && store && (persist || !!opts.linkRowIds) && (opts.captureStillOn?.() ?? true) ? [] : void 0;
|
|
3251
|
+
const callStart = now();
|
|
3252
|
+
const outcome = await callWithin(
|
|
3253
|
+
provider,
|
|
3254
|
+
misses.map((it) => it.req),
|
|
3255
|
+
remaining,
|
|
3256
|
+
sink
|
|
3257
|
+
);
|
|
3258
|
+
const providerLatencyMs = now() - callStart;
|
|
3259
|
+
const sample = !misses.some((it) => it.req.surface === "test");
|
|
3260
|
+
if (!outcome.ok) {
|
|
3261
|
+
const kind = outcome.kind;
|
|
3262
|
+
log(`adjudicator: ${r.provider} ${kind}${outcome.status ? ` ${outcome.status}` : ""}`);
|
|
3263
|
+
if (store)
|
|
3264
|
+
record(
|
|
3265
|
+
store,
|
|
3266
|
+
r.provider,
|
|
3267
|
+
now(),
|
|
3268
|
+
log,
|
|
3269
|
+
locks,
|
|
3270
|
+
() => recordFailure(store, r.provider, cfg, {
|
|
3271
|
+
now: now(),
|
|
3272
|
+
kind,
|
|
3273
|
+
status: outcome.status,
|
|
3274
|
+
latencyMs: providerLatencyMs,
|
|
3275
|
+
budgetMs,
|
|
3276
|
+
halfOpen,
|
|
3277
|
+
keyFingerprint: keyFp,
|
|
3278
|
+
sample,
|
|
3279
|
+
lockWaitMs: locks.wait(LOCK_WAIT_FLOOR_MS)
|
|
3280
|
+
})
|
|
3281
|
+
);
|
|
3282
|
+
for (const it of misses) {
|
|
3283
|
+
it.verdict = heuristicVerdict(it, r.provider, "fallback", kind);
|
|
3284
|
+
it.verdict.providerLatencyMs = providerLatencyMs;
|
|
3285
|
+
}
|
|
3286
|
+
const failed = finish(items, opts, start, now, persist, locks);
|
|
3287
|
+
if (sink && store) writeExchanges(store, sink, misses, failed, opts, start, budgetMs, locks);
|
|
3288
|
+
return failed;
|
|
3289
|
+
}
|
|
3290
|
+
const res = outcome.result;
|
|
3291
|
+
if (store)
|
|
3292
|
+
record(
|
|
3293
|
+
store,
|
|
3294
|
+
r.provider,
|
|
3295
|
+
now(),
|
|
3296
|
+
log,
|
|
3297
|
+
locks,
|
|
3298
|
+
() => recordSuccess(store, r.provider, cfg, {
|
|
3299
|
+
now: now(),
|
|
3300
|
+
// The time this call measured, not what the provider reports: the budget governs
|
|
3301
|
+
// the wall clock, and a provider cannot report itself slower or faster than it was.
|
|
3302
|
+
latencyMs: providerLatencyMs,
|
|
3303
|
+
costUsd: res.usage.costUsd,
|
|
3304
|
+
budgetMs,
|
|
3305
|
+
sample,
|
|
3306
|
+
lockWaitMs: locks.wait(LOCK_WAIT_FLOOR_MS)
|
|
3307
|
+
})
|
|
3308
|
+
);
|
|
3309
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
3310
|
+
for (const a of res.answers) if (!byKey.has(a.key)) byKey.set(a.key, a);
|
|
3311
|
+
const answered = misses.filter((it) => byKey.has(it.req.key)).length;
|
|
3312
|
+
const share = answered ? 1 / answered : 0;
|
|
3313
|
+
const u = res.usage;
|
|
3314
|
+
const usage = {
|
|
3315
|
+
inTok: Math.round(u.inTok * share),
|
|
3316
|
+
outTok: Math.round(u.outTok * share),
|
|
3317
|
+
costUsd: u.costUsd === null ? null : u.costUsd * share
|
|
3318
|
+
};
|
|
3319
|
+
for (const it of misses) {
|
|
3320
|
+
const answer = byKey.get(it.req.key);
|
|
3321
|
+
if (!answer) {
|
|
3322
|
+
it.verdict = heuristicVerdict(it, r.provider, "fallback", "skipped");
|
|
3323
|
+
it.verdict.providerLatencyMs = providerLatencyMs;
|
|
3324
|
+
continue;
|
|
3325
|
+
}
|
|
3326
|
+
it.verdict = blend(it.h, answer, it.bias, cfg.weights, {
|
|
3327
|
+
key: it.req.key,
|
|
3328
|
+
provider: r.provider,
|
|
3329
|
+
providerStatus: "ok",
|
|
3330
|
+
source: "blend",
|
|
3331
|
+
tiers: cfg.tiers,
|
|
3332
|
+
providerLatencyMs,
|
|
3333
|
+
usage
|
|
3334
|
+
});
|
|
3335
|
+
it.providerModel = res.model || r.model;
|
|
3336
|
+
if (store) {
|
|
3337
|
+
const v = it.verdict;
|
|
3338
|
+
safe(
|
|
3339
|
+
() => locks.run(store, 0, () => store.cachePut(it.cacheKey, v, r.configured, now())),
|
|
3340
|
+
log
|
|
3341
|
+
);
|
|
3342
|
+
}
|
|
3343
|
+
}
|
|
3344
|
+
if (store && answered && (opts.random ?? Math.random)() < answered / PRUNE_EVERY)
|
|
3345
|
+
safe(
|
|
3346
|
+
() => locks.run(
|
|
3347
|
+
store,
|
|
3348
|
+
0,
|
|
3349
|
+
() => store.cachePrune(cfg.cache.maxEntries, cfg.cache.ttlHours * 36e5, now())
|
|
3350
|
+
),
|
|
3351
|
+
log
|
|
3352
|
+
);
|
|
3353
|
+
const out = finish(items, opts, start, now, persist, locks);
|
|
3354
|
+
if (sink && store) writeExchanges(store, sink, misses, out, opts, start, budgetMs, locks);
|
|
3355
|
+
return out;
|
|
3356
|
+
} catch (err) {
|
|
3357
|
+
log(`adjudicator: engine error ${err?.message ?? String(err)}`);
|
|
3358
|
+
for (const [index, req] of reqs.entries()) {
|
|
3359
|
+
const it = items[index] ?? safePrepare(req, index, opts);
|
|
3360
|
+
if (!it.verdict) it.verdict = heuristicVerdict(it, "heuristic", "heuristic", "n/a");
|
|
3361
|
+
items[index] = it;
|
|
3362
|
+
}
|
|
3363
|
+
return items.map((it) => ({
|
|
3364
|
+
request: it.req,
|
|
3365
|
+
verdict: {
|
|
3366
|
+
...it.verdict,
|
|
3367
|
+
latencyMs: now() - start,
|
|
3368
|
+
heuristicComplexity: it.h.complexity
|
|
3369
|
+
},
|
|
3370
|
+
cacheKey: it.cacheKey,
|
|
3371
|
+
features: it.features,
|
|
3372
|
+
...it.units ? { units: it.units } : {}
|
|
3373
|
+
}));
|
|
3374
|
+
}
|
|
3375
|
+
}
|
|
3376
|
+
async function adjudicateOne(req, opts) {
|
|
3377
|
+
const [out] = await adjudicate([req], opts);
|
|
3378
|
+
return out;
|
|
3379
|
+
}
|
|
3380
|
+
function prepare(req, index, opts) {
|
|
3381
|
+
const cfg = opts.config;
|
|
3382
|
+
const catalogue = { tiers: cfg.tiers };
|
|
3383
|
+
const tools = toolsOf(opts);
|
|
3384
|
+
if (tools) catalogue.tools = tools;
|
|
3385
|
+
const roles = req.hints?.roles ?? opts.roles;
|
|
3386
|
+
if (roles?.length) catalogue.roles = roles;
|
|
3387
|
+
const weights = { tierCenters: cfg.weights.tierCenters, features: cfg.weights.features };
|
|
3388
|
+
const brief = byUnits(req.surface, opts.units) ? scoreUnits(req.text, req.hints, weights, catalogue) : void 0;
|
|
3389
|
+
const features = brief?.features ?? extractFeatures(req.text, req.hints);
|
|
3390
|
+
const h = brief?.set ?? scoreHeuristic(features, weights, catalogue);
|
|
3391
|
+
let bias = {};
|
|
3392
|
+
if (cfg.learning.enabled && opts.bias)
|
|
3393
|
+
bias = typeof opts.bias === "function" ? opts.bias(req, features, h) : opts.bias;
|
|
3394
|
+
const it = { req, index, features, h, bias, cacheKey: "", cfg };
|
|
3395
|
+
if (brief) it.units = brief.units;
|
|
3396
|
+
return it;
|
|
3397
|
+
}
|
|
3398
|
+
function safePrepare(req, index, opts) {
|
|
3399
|
+
try {
|
|
3400
|
+
return prepare(req, index, { ...opts, bias: void 0 });
|
|
3401
|
+
} catch {
|
|
3402
|
+
const features = extractFeatures("", void 0);
|
|
3403
|
+
const h = scoreHeuristic(features);
|
|
3404
|
+
return { req, index, features, h, bias: {}, cacheKey: "" };
|
|
3405
|
+
}
|
|
3406
|
+
}
|
|
3407
|
+
function heuristicVerdict(it, provider, source, status) {
|
|
3408
|
+
return blend(it.h, void 0, it.bias, it.cfg?.weights ?? {}, {
|
|
3409
|
+
tiers: it.cfg?.tiers ?? DEFAULT_TIERS,
|
|
3410
|
+
key: it.req.key,
|
|
3411
|
+
provider,
|
|
3412
|
+
providerStatus: status,
|
|
3413
|
+
source
|
|
3414
|
+
});
|
|
3415
|
+
}
|
|
3416
|
+
function finish(items, opts, start, now, persist, locks) {
|
|
3417
|
+
const log = opts.log ?? (() => {
|
|
3418
|
+
});
|
|
3419
|
+
const cfg = opts.config;
|
|
3420
|
+
const latencyMs = now() - start;
|
|
3421
|
+
return items.map((it) => {
|
|
3422
|
+
const verdict = {
|
|
3423
|
+
...it.verdict,
|
|
3424
|
+
latencyMs,
|
|
3425
|
+
heuristicComplexity: it.h.complexity
|
|
3426
|
+
};
|
|
3427
|
+
const out = {
|
|
3428
|
+
request: it.req,
|
|
3429
|
+
verdict,
|
|
3430
|
+
cacheKey: it.cacheKey,
|
|
3431
|
+
features: it.features
|
|
3432
|
+
};
|
|
3433
|
+
if (it.queued) out.queued = true;
|
|
3434
|
+
if (it.providerModel) out.providerModel = it.providerModel;
|
|
3435
|
+
if (it.units) out.units = it.units;
|
|
3436
|
+
if (persist && opts.store) {
|
|
3437
|
+
const store = opts.store;
|
|
3438
|
+
const cols = verdictColumns(verdict, {
|
|
3439
|
+
features: it.features,
|
|
3440
|
+
providerModel: it.providerModel,
|
|
3441
|
+
units: it.units
|
|
3442
|
+
});
|
|
3443
|
+
const row = {
|
|
3444
|
+
...cols,
|
|
3445
|
+
surface: it.req.surface,
|
|
3446
|
+
inputHash: inputHash(it.req.text),
|
|
3447
|
+
inputHead: cfg.privacy.storeHead ? head(it.req.text) : null,
|
|
3448
|
+
inputChars: it.req.text.length,
|
|
3449
|
+
...opts.row,
|
|
3450
|
+
...opts.rows?.[it.index]
|
|
3451
|
+
};
|
|
3452
|
+
const id = safe(() => locks.run(store, LOCK_WAIT_FLOOR_MS, () => store.insert(row)), log);
|
|
3453
|
+
if (id !== void 0) {
|
|
3454
|
+
out.rowId = id;
|
|
3455
|
+
verdict.id = id;
|
|
3456
|
+
}
|
|
3457
|
+
}
|
|
3458
|
+
return out;
|
|
3459
|
+
});
|
|
3460
|
+
}
|
|
3461
|
+
async function callWithin(provider, reqs, ms, capture) {
|
|
3462
|
+
const ac = new AbortController();
|
|
3463
|
+
let timer;
|
|
3464
|
+
const timeout = new Promise((resolve) => {
|
|
3465
|
+
timer = setTimeout(() => {
|
|
3466
|
+
ac.abort();
|
|
3467
|
+
resolve({ ok: false, kind: "timeout" });
|
|
3468
|
+
}, ms);
|
|
3469
|
+
timer.unref?.();
|
|
3470
|
+
});
|
|
3471
|
+
const attempt = (async () => {
|
|
3472
|
+
try {
|
|
3473
|
+
const result = await provider.adjudicate(
|
|
3474
|
+
reqs,
|
|
3475
|
+
capture ? { signal: ac.signal, deadlineMs: ms, capture } : { signal: ac.signal, deadlineMs: ms }
|
|
3476
|
+
);
|
|
3477
|
+
return { ok: true, result };
|
|
3478
|
+
} catch (err) {
|
|
3479
|
+
if (err instanceof ProviderError) {
|
|
3480
|
+
const o = { ok: false, kind: err.kind };
|
|
3481
|
+
if (err.status !== void 0) o.status = err.status;
|
|
3482
|
+
return o;
|
|
3483
|
+
}
|
|
3484
|
+
if (ac.signal.aborted || err?.name === "AbortError")
|
|
3485
|
+
return { ok: false, kind: "timeout" };
|
|
3486
|
+
return { ok: false, kind: "invalid" };
|
|
3487
|
+
}
|
|
3488
|
+
})();
|
|
3489
|
+
try {
|
|
3490
|
+
return await Promise.race([attempt, timeout]);
|
|
3491
|
+
} finally {
|
|
3492
|
+
if (timer) clearTimeout(timer);
|
|
3493
|
+
}
|
|
3494
|
+
}
|
|
3495
|
+
function peekGate(store, r, cfg, now, keyFp) {
|
|
3496
|
+
const s = store.providerState(r.provider);
|
|
3497
|
+
if (!s) return void 0;
|
|
3498
|
+
if (authLockout(s, now, keyFp)?.locked) return "auth_failed";
|
|
3499
|
+
if ((s.state === "open" || s.state === "half_open") && s.retryAt !== null && now < s.retryAt)
|
|
3500
|
+
return "breaker_open";
|
|
3501
|
+
if (s.state === "slow" && s.retryAt !== null && now < s.retryAt) return "slow_guard";
|
|
3502
|
+
if (r.remote && overBudget(s, cfg, now)) return "over_budget";
|
|
3503
|
+
return void 0;
|
|
3504
|
+
}
|
|
3505
|
+
function buildJob(it, cfg, opts, budgetMs) {
|
|
3506
|
+
const job = {
|
|
3507
|
+
v: 1,
|
|
3508
|
+
projectRoot: opts.projectRoot ?? process.cwd(),
|
|
3509
|
+
request: it.req,
|
|
3510
|
+
cacheKey: it.cacheKey,
|
|
3511
|
+
config: cfg,
|
|
3512
|
+
context: opts.context ?? "hook",
|
|
3513
|
+
budgetMs: 2 * budgetMs,
|
|
3514
|
+
bias: it.bias
|
|
3515
|
+
};
|
|
3516
|
+
const tools = toolsOf(opts);
|
|
3517
|
+
if (tools) job.tools = [...tools];
|
|
3518
|
+
if (opts.roles) job.roles = opts.roles.map((r) => ({ ...r }));
|
|
3519
|
+
if (cfg.privacy.captureExchanges === true) job.queuedAt = (opts.now ?? Date.now)();
|
|
3520
|
+
return job;
|
|
3521
|
+
}
|
|
3522
|
+
function writeExchanges(store, sink, misses, out, opts, start, budgetMs, locks) {
|
|
3523
|
+
if (!sink.length) return;
|
|
3524
|
+
const now = opts.now ?? Date.now;
|
|
3525
|
+
const log = opts.log ?? (() => {
|
|
3526
|
+
});
|
|
3527
|
+
try {
|
|
3528
|
+
if (opts.captureStillOn && !opts.captureStillOn()) return;
|
|
3529
|
+
const rowsByKey = /* @__PURE__ */ new Map();
|
|
3530
|
+
for (const it of misses) {
|
|
3531
|
+
const id = out[it.index]?.rowId ?? opts.linkRowIds?.[it.index];
|
|
3532
|
+
if (id === void 0) continue;
|
|
3533
|
+
const ids = rowsByKey.get(it.req.key);
|
|
3534
|
+
if (ids) ids.push(id);
|
|
3535
|
+
else rowsByKey.set(it.req.key, [id]);
|
|
3536
|
+
}
|
|
3537
|
+
const callId = `${start.toString(36)}${Math.random().toString(36).slice(2, 8)}`;
|
|
3538
|
+
const t = now();
|
|
3539
|
+
const xs = sink.map((pe) => {
|
|
3540
|
+
const x = pe.http;
|
|
3541
|
+
const pending = x.outcome === "pending";
|
|
3542
|
+
const outcome = pending ? "timeout" : x.outcome;
|
|
3543
|
+
const res = pending ? null : x.response;
|
|
3544
|
+
return {
|
|
3545
|
+
ts: x.sentAt,
|
|
3546
|
+
callId,
|
|
3547
|
+
batch: pe.batch,
|
|
3548
|
+
provider: pe.provider,
|
|
3549
|
+
model: pe.model,
|
|
3550
|
+
surface: misses[0]?.req.surface ?? null,
|
|
3551
|
+
context: opts.context ?? "hook",
|
|
3552
|
+
method: x.method,
|
|
3553
|
+
target: x.target,
|
|
3554
|
+
rowIds: [...new Set(pe.itemKeys.flatMap((k) => rowsByKey.get(k) ?? []))],
|
|
3555
|
+
items: pe.itemKeys.length,
|
|
3556
|
+
outcome,
|
|
3557
|
+
httpStatus: x.status,
|
|
3558
|
+
timedOut: outcome === "timeout",
|
|
3559
|
+
attempts: x.attempts,
|
|
3560
|
+
waitMs: Math.max(0, x.sentAt - start),
|
|
3561
|
+
jobWaitMs: opts.jobWaitMs ?? null,
|
|
3562
|
+
ttfbMs: x.ttfbMs,
|
|
3563
|
+
durationMs: pending ? Math.max(0, t - x.sentAt) : x.durationMs,
|
|
3564
|
+
retryWaitMs: x.retryWaitMs,
|
|
3565
|
+
deadlineMs: x.deadlineMs,
|
|
3566
|
+
budgetMs,
|
|
3567
|
+
tokensIn: pe.tokensIn,
|
|
3568
|
+
tokensOut: pe.tokensOut,
|
|
3569
|
+
costUsd: pe.costUsd,
|
|
3570
|
+
requestBody: x.request.text,
|
|
3571
|
+
requestBytes: x.request.bytes,
|
|
3572
|
+
requestTruncated: x.request.truncated,
|
|
3573
|
+
responseBody: res ? res.text : null,
|
|
3574
|
+
responseBytes: res ? res.bytes : null,
|
|
3575
|
+
responseTruncated: res ? res.truncated : false,
|
|
3576
|
+
error: x.error
|
|
3577
|
+
};
|
|
3578
|
+
});
|
|
3579
|
+
locks.run(store, 0, () => store.exchangeInsert(xs));
|
|
3580
|
+
} catch (err) {
|
|
3581
|
+
log(`adjudicator: exchange capture skipped (${err?.message ?? String(err)})`);
|
|
3582
|
+
}
|
|
3583
|
+
}
|
|
3584
|
+
var LockBudget = class {
|
|
3585
|
+
constructor(deadline, now) {
|
|
3586
|
+
this.deadline = deadline;
|
|
3587
|
+
this.now = now;
|
|
3588
|
+
}
|
|
3589
|
+
deadline;
|
|
3590
|
+
now;
|
|
3591
|
+
busy = false;
|
|
3592
|
+
wait(floor = 0) {
|
|
3593
|
+
if (this.busy) return floor;
|
|
3594
|
+
return Math.max(floor, Math.min(LOCK_WAIT_MAX_MS, Math.floor(this.deadline - this.now())));
|
|
3595
|
+
}
|
|
3596
|
+
/** `fn`, noting a busy database before rethrowing its error. */
|
|
3597
|
+
guard(fn) {
|
|
3598
|
+
try {
|
|
3599
|
+
return fn();
|
|
3600
|
+
} catch (err) {
|
|
3601
|
+
if (isBusy(err)) this.busy = true;
|
|
3602
|
+
throw err;
|
|
3603
|
+
}
|
|
3604
|
+
}
|
|
3605
|
+
/** `fn` with every lock wait capped at wait(floor). */
|
|
3606
|
+
run(store, floor, fn) {
|
|
3607
|
+
const ms = this.wait(floor);
|
|
3608
|
+
return this.guard(() => bounded(store, ms, fn));
|
|
3609
|
+
}
|
|
3610
|
+
};
|
|
3611
|
+
function isBusy(err) {
|
|
3612
|
+
const code = err?.code;
|
|
3613
|
+
if (typeof code === "string" && /^SQLITE_(BUSY|LOCKED)/.test(code)) return true;
|
|
3614
|
+
return /database is (locked|busy)|SQLITE_BUSY/i.test(err?.message ?? "");
|
|
3615
|
+
}
|
|
3616
|
+
function bounded(store, ms, fn) {
|
|
3617
|
+
return store.withLockWait ? store.withLockWait(ms, fn) : fn();
|
|
3618
|
+
}
|
|
3619
|
+
function record(store, provider, now, log, locks, fn) {
|
|
3620
|
+
try {
|
|
3621
|
+
locks.guard(fn);
|
|
3622
|
+
} catch (err) {
|
|
3623
|
+
const u = unrecorded(store, provider, now);
|
|
3624
|
+
log(
|
|
3625
|
+
`adjudicator: ${provider} breaker state not written (${err?.message ?? String(err)}); unrecorded today: ${u.calls} call(s), $${u.spendUsd.toFixed(6)}`
|
|
3626
|
+
);
|
|
3627
|
+
}
|
|
3628
|
+
}
|
|
3629
|
+
function safe(fn, log) {
|
|
3630
|
+
try {
|
|
3631
|
+
return fn();
|
|
3632
|
+
} catch (err) {
|
|
3633
|
+
log(`adjudicator: ${err?.message ?? String(err)}`);
|
|
3634
|
+
return void 0;
|
|
3635
|
+
}
|
|
3636
|
+
}
|
|
3637
|
+
|
|
3638
|
+
// src/adjudicator/async.ts
|
|
3639
|
+
var ASYNC_JOB_MAX_MS = 3e4;
|
|
3640
|
+
function spawnAsyncJob(job, deps = {}) {
|
|
3641
|
+
try {
|
|
3642
|
+
const script = deps.script ?? process.argv[1];
|
|
3643
|
+
if (!script) return false;
|
|
3644
|
+
const child = (deps.spawn ?? nodeSpawn)(
|
|
3645
|
+
deps.execPath ?? process.execPath,
|
|
3646
|
+
[script, "--adjudicate"],
|
|
3647
|
+
{
|
|
3648
|
+
detached: true,
|
|
3649
|
+
stdio: ["pipe", "ignore", "ignore"],
|
|
3650
|
+
env: deps.env ?? process.env
|
|
3651
|
+
}
|
|
3652
|
+
);
|
|
3653
|
+
child.on("error", () => {
|
|
3654
|
+
});
|
|
3655
|
+
child.stdin?.on("error", () => {
|
|
3656
|
+
});
|
|
3657
|
+
child.stdin?.end(JSON.stringify(job));
|
|
3658
|
+
child.unref();
|
|
3659
|
+
return true;
|
|
3660
|
+
} catch {
|
|
3661
|
+
return false;
|
|
3662
|
+
}
|
|
3663
|
+
}
|
|
3664
|
+
function parseJob(raw) {
|
|
3665
|
+
try {
|
|
3666
|
+
const j = JSON.parse(raw);
|
|
3667
|
+
const req = j?.request;
|
|
3668
|
+
if (j?.v !== 1 || typeof j.projectRoot !== "string" || typeof j.cacheKey !== "string")
|
|
3669
|
+
return void 0;
|
|
3670
|
+
if (!req || typeof req.text !== "string" || typeof req.key !== "string") return void 0;
|
|
3671
|
+
if (!SURFACES.includes(req.surface)) return void 0;
|
|
3672
|
+
const config = AdjudicatorConfigSchema.safeParse(j.config);
|
|
3673
|
+
if (!config.success) return void 0;
|
|
3674
|
+
const budgetMs = typeof j.budgetMs === "number" && j.budgetMs > 0 ? j.budgetMs : 2400;
|
|
3675
|
+
const context = j.context === "server" || j.context === "cli" ? j.context : "hook";
|
|
3676
|
+
const job = {
|
|
3677
|
+
v: 1,
|
|
3678
|
+
projectRoot: j.projectRoot,
|
|
3679
|
+
request: req,
|
|
3680
|
+
cacheKey: j.cacheKey,
|
|
3681
|
+
config: config.data,
|
|
3682
|
+
context,
|
|
3683
|
+
budgetMs: Math.min(budgetMs, ASYNC_JOB_MAX_MS - 1e3)
|
|
3684
|
+
};
|
|
3685
|
+
if (typeof j.rowId === "number") job.rowId = j.rowId;
|
|
3686
|
+
if (j.bias && typeof j.bias === "object") job.bias = j.bias;
|
|
3687
|
+
if (Array.isArray(j.tools)) job.tools = j.tools;
|
|
3688
|
+
if (Array.isArray(j.roles)) job.roles = j.roles;
|
|
3689
|
+
if (typeof j.queuedAt === "number" && Number.isFinite(j.queuedAt)) job.queuedAt = j.queuedAt;
|
|
3690
|
+
return job;
|
|
3691
|
+
} catch {
|
|
3692
|
+
return void 0;
|
|
3693
|
+
}
|
|
3694
|
+
}
|
|
3695
|
+
async function runAsyncJob(raw, deps = {}) {
|
|
3696
|
+
const log = deps.log ?? (() => {
|
|
3697
|
+
});
|
|
3698
|
+
const childStart = (deps.now ?? Date.now)();
|
|
3699
|
+
try {
|
|
3700
|
+
const job = parseJob(raw);
|
|
3701
|
+
if (!job) return { ok: false, reason: "invalid job" };
|
|
3702
|
+
if (!job.config.enabled) return { ok: false, reason: "disabled" };
|
|
3703
|
+
const store = deps.store ?? await openStore(job.projectRoot);
|
|
3704
|
+
if (!store) return { ok: false, reason: "no store" };
|
|
3705
|
+
const opts = {
|
|
3706
|
+
config: job.config,
|
|
3707
|
+
store,
|
|
3708
|
+
context: job.context,
|
|
3709
|
+
call: "inline",
|
|
3710
|
+
budgetMs: job.budgetMs,
|
|
3711
|
+
persist: false,
|
|
3712
|
+
log
|
|
3713
|
+
};
|
|
3714
|
+
if (job.bias) opts.bias = job.bias;
|
|
3715
|
+
if (job.tools) opts.tools = job.tools;
|
|
3716
|
+
if (job.roles) opts.roles = job.roles;
|
|
3717
|
+
if (deps.env) opts.env = deps.env;
|
|
3718
|
+
if (deps.now) opts.now = deps.now;
|
|
3719
|
+
if (deps.provider !== void 0) opts.provider = deps.provider;
|
|
3720
|
+
if (deps.fetch) opts.deps = { fetch: deps.fetch };
|
|
3721
|
+
if (job.rowId !== void 0) opts.linkRowIds = [job.rowId];
|
|
3722
|
+
if (job.queuedAt !== void 0) opts.jobWaitMs = Math.max(0, childStart - job.queuedAt);
|
|
3723
|
+
if (job.config.privacy.captureExchanges === true) {
|
|
3724
|
+
opts.captureStillOn = () => captureStillOn(job.projectRoot);
|
|
3725
|
+
}
|
|
3726
|
+
const [out] = await adjudicate([job.request], opts);
|
|
3727
|
+
if (!out) return { ok: false, reason: "no verdict" };
|
|
3728
|
+
const v = out.verdict;
|
|
3729
|
+
const res = { ok: true, source: v.source, providerStatus: v.providerStatus };
|
|
3730
|
+
if (job.rowId !== void 0) {
|
|
3731
|
+
try {
|
|
3732
|
+
if (v.source === "blend" || v.source === "cache") {
|
|
3733
|
+
const { latencyMs: _childLatency, ...cols } = verdictColumns(v, {
|
|
3734
|
+
features: out.features,
|
|
3735
|
+
providerModel: out.providerModel,
|
|
3736
|
+
units: out.units
|
|
3737
|
+
});
|
|
3738
|
+
res.rowUpdated = store.update(job.rowId, cols);
|
|
3739
|
+
} else {
|
|
3740
|
+
res.rowUpdated = store.update(job.rowId, {
|
|
3741
|
+
providerStatus: v.providerStatus,
|
|
3742
|
+
providerLatencyMs: v.providerLatencyMs !== void 0 ? Math.round(v.providerLatencyMs) : null
|
|
3743
|
+
});
|
|
3744
|
+
}
|
|
3745
|
+
} catch (err) {
|
|
3746
|
+
log(`adjudicator: async row update failed: ${err.message}`);
|
|
3747
|
+
res.rowUpdated = false;
|
|
3748
|
+
}
|
|
3749
|
+
}
|
|
3750
|
+
return res;
|
|
3751
|
+
} catch (err) {
|
|
3752
|
+
log(`adjudicator: async job failed: ${err?.message ?? String(err)}`);
|
|
3753
|
+
return { ok: false, reason: "error" };
|
|
3754
|
+
}
|
|
3755
|
+
}
|
|
3756
|
+
function captureStillOn(projectRoot) {
|
|
3757
|
+
try {
|
|
3758
|
+
return loadConfig(projectRoot).adjudicator.privacy.captureExchanges === true;
|
|
3759
|
+
} catch {
|
|
3760
|
+
return false;
|
|
3761
|
+
}
|
|
3762
|
+
}
|
|
3763
|
+
async function openStore(projectRoot) {
|
|
3764
|
+
return getStorage(getContext(projectRoot), { vectors: false }).adjudicator;
|
|
3765
|
+
}
|
|
3766
|
+
|
|
3767
|
+
export {
|
|
3768
|
+
extractFeatures,
|
|
3769
|
+
complexityBucket,
|
|
3770
|
+
topIntent,
|
|
3771
|
+
resolveAdjudicator,
|
|
3772
|
+
briefUnitsOf,
|
|
3773
|
+
cacheKeyFor,
|
|
3774
|
+
adjudicateOne,
|
|
3775
|
+
ASYNC_JOB_MAX_MS,
|
|
3776
|
+
spawnAsyncJob,
|
|
3777
|
+
parseJob,
|
|
3778
|
+
runAsyncJob
|
|
3779
|
+
};
|
|
3780
|
+
//# sourceMappingURL=hook-chunk-RYZAFUTT.js.map
|