agent-nuvira 3.1.3 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/agents/reasoner.d.ts +8 -0
- package/dist/agents/agents/reasoner.d.ts.map +1 -1
- package/dist/agents/agents/reasoner.js +53 -2
- package/dist/agents/agents/reasoner.js.map +1 -1
- package/dist/agents/agents/writer.d.ts +24 -0
- package/dist/agents/agents/writer.d.ts.map +1 -1
- package/dist/agents/agents/writer.js +194 -0
- package/dist/agents/agents/writer.js.map +1 -1
- package/dist/agents/composite-plan.d.ts +143 -0
- package/dist/agents/composite-plan.d.ts.map +1 -0
- package/dist/agents/composite-plan.js +399 -0
- package/dist/agents/composite-plan.js.map +1 -0
- package/dist/agents/long-form-plan.d.ts +156 -0
- package/dist/agents/long-form-plan.d.ts.map +1 -0
- package/dist/agents/long-form-plan.js +274 -0
- package/dist/agents/long-form-plan.js.map +1 -0
- package/dist/agents/orchestrator.d.ts +107 -0
- package/dist/agents/orchestrator.d.ts.map +1 -1
- package/dist/agents/orchestrator.js +501 -35
- package/dist/agents/orchestrator.js.map +1 -1
- package/dist/agents/prompt-assembly.d.ts +8 -0
- package/dist/agents/prompt-assembly.d.ts.map +1 -1
- package/dist/agents/prompt-assembly.js +17 -0
- package/dist/agents/prompt-assembly.js.map +1 -1
- package/dist/cli/chat.d.ts +27 -0
- package/dist/cli/chat.d.ts.map +1 -1
- package/dist/cli/chat.js +116 -6
- package/dist/cli/chat.js.map +1 -1
- package/dist/cli/execute.d.ts +12 -0
- package/dist/cli/execute.d.ts.map +1 -1
- package/dist/cli/execute.js +105 -2
- package/dist/cli/execute.js.map +1 -1
- package/dist/cli/models.d.ts.map +1 -1
- package/dist/cli/models.js +10 -0
- package/dist/cli/models.js.map +1 -1
- package/dist/cli/trace.d.ts.map +1 -1
- package/dist/cli/trace.js +27 -0
- package/dist/cli/trace.js.map +1 -1
- package/dist/gateway/registry.d.ts +31 -0
- package/dist/gateway/registry.d.ts.map +1 -1
- package/dist/gateway/registry.js +143 -13
- package/dist/gateway/registry.js.map +1 -1
- package/dist/inference/model-entitlement.d.ts +46 -0
- package/dist/inference/model-entitlement.d.ts.map +1 -0
- package/dist/inference/model-entitlement.js +98 -0
- package/dist/inference/model-entitlement.js.map +1 -0
- package/dist/learning/autonomy-policy.d.ts +334 -0
- package/dist/learning/autonomy-policy.d.ts.map +1 -0
- package/dist/learning/autonomy-policy.js +500 -0
- package/dist/learning/autonomy-policy.js.map +1 -0
- package/dist/learning/credential-fingerprint.d.ts +58 -0
- package/dist/learning/credential-fingerprint.d.ts.map +1 -0
- package/dist/learning/credential-fingerprint.js +126 -0
- package/dist/learning/credential-fingerprint.js.map +1 -0
- package/dist/learning/deliverable-class.d.ts +130 -0
- package/dist/learning/deliverable-class.d.ts.map +1 -0
- package/dist/learning/deliverable-class.js +432 -0
- package/dist/learning/deliverable-class.js.map +1 -0
- package/dist/learning/long-form.d.ts +252 -0
- package/dist/learning/long-form.d.ts.map +1 -0
- package/dist/learning/long-form.js +510 -0
- package/dist/learning/long-form.js.map +1 -0
- package/dist/learning/model-first-router.d.ts +17 -0
- package/dist/learning/model-first-router.d.ts.map +1 -1
- package/dist/learning/model-first-router.js +27 -0
- package/dist/learning/model-first-router.js.map +1 -1
- package/dist/learning/model-registry.d.ts +66 -4
- package/dist/learning/model-registry.d.ts.map +1 -1
- package/dist/learning/model-registry.js +67 -6
- package/dist/learning/model-registry.js.map +1 -1
- package/dist/learning/model-warmup.d.ts +97 -2
- package/dist/learning/model-warmup.d.ts.map +1 -1
- package/dist/learning/model-warmup.js +165 -60
- package/dist/learning/model-warmup.js.map +1 -1
- package/dist/learning/prompt-layers.d.ts +61 -0
- package/dist/learning/prompt-layers.d.ts.map +1 -0
- package/dist/learning/prompt-layers.js +140 -0
- package/dist/learning/prompt-layers.js.map +1 -0
- package/dist/learning/provider-limits.d.ts +66 -0
- package/dist/learning/provider-limits.d.ts.map +1 -0
- package/dist/learning/provider-limits.js +184 -0
- package/dist/learning/provider-limits.js.map +1 -0
- package/dist/learning/reasoning-trace.d.ts +36 -1
- package/dist/learning/reasoning-trace.d.ts.map +1 -1
- package/dist/learning/reasoning-trace.js +36 -2
- package/dist/learning/reasoning-trace.js.map +1 -1
- package/dist/learning/resilient-call.d.ts +36 -1
- package/dist/learning/resilient-call.d.ts.map +1 -1
- package/dist/learning/resilient-call.js +80 -5
- package/dist/learning/resilient-call.js.map +1 -1
- package/dist/learning/unattended-job.d.ts +293 -0
- package/dist/learning/unattended-job.d.ts.map +1 -0
- package/dist/learning/unattended-job.js +544 -0
- package/dist/learning/unattended-job.js.map +1 -0
- package/dist/learning/unattended-progress.d.ts +95 -0
- package/dist/learning/unattended-progress.d.ts.map +1 -0
- package/dist/learning/unattended-progress.js +147 -0
- package/dist/learning/unattended-progress.js.map +1 -0
- package/dist/learning/working-state.d.ts +109 -0
- package/dist/learning/working-state.d.ts.map +1 -0
- package/dist/learning/working-state.js +244 -0
- package/dist/learning/working-state.js.map +1 -0
- package/dist/nlu/conversation-gate.d.ts +24 -0
- package/dist/nlu/conversation-gate.d.ts.map +1 -1
- package/dist/nlu/conversation-gate.js +47 -3
- package/dist/nlu/conversation-gate.js.map +1 -1
- package/dist/tools/coding-tools.d.ts.map +1 -1
- package/dist/tools/coding-tools.js +95 -19
- package/dist/tools/coding-tools.js.map +1 -1
- package/dist/tools/edit-verification.d.ts +125 -0
- package/dist/tools/edit-verification.d.ts.map +1 -0
- package/dist/tools/edit-verification.js +237 -0
- package/dist/tools/edit-verification.js.map +1 -0
- package/dist/tools/git-tool.d.ts.map +1 -1
- package/dist/tools/git-tool.js +37 -4
- package/dist/tools/git-tool.js.map +1 -1
- package/dist/tools/registry.d.ts +30 -2
- package/dist/tools/registry.d.ts.map +1 -1
- package/dist/tools/registry.js +40 -11
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/run-cli.d.ts.map +1 -1
- package/dist/tools/run-cli.js +40 -13
- package/dist/tools/run-cli.js.map +1 -1
- package/dist/tools/run-terminal.d.ts +6 -1
- package/dist/tools/run-terminal.d.ts.map +1 -1
- package/dist/tools/run-terminal.js +158 -18
- package/dist/tools/run-terminal.js.map +1 -1
- package/dist/tools/tool-loop.d.ts +35 -0
- package/dist/tools/tool-loop.d.ts.map +1 -1
- package/dist/tools/tool-loop.js +141 -1
- package/dist/tools/tool-loop.js.map +1 -1
- package/dist/web-dashboard/chat-console.d.ts +6 -0
- package/dist/web-dashboard/chat-console.d.ts.map +1 -1
- package/dist/web-dashboard/chat-console.js.map +1 -1
- package/dist/web-dashboard/chat-retry.d.ts.map +1 -1
- package/dist/web-dashboard/chat-retry.js +10 -2
- package/dist/web-dashboard/chat-retry.js.map +1 -1
- package/dist/web-dashboard/server.d.ts.map +1 -1
- package/dist/web-dashboard/server.js +80 -8
- package/dist/web-dashboard/server.js.map +1 -1
- package/dist/web-dashboard/src/types.d.ts +81 -4
- package/dist/web-dashboard/src/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/web-dashboard/public/assets/{index-Co7Hk2FT.js → index-kCUkORm7.js} +2 -2
- package/src/web-dashboard/public/assets/{index-Co7Hk2FT.js.map → index-kCUkORm7.js.map} +1 -1
- package/src/web-dashboard/public/index.html +1 -1
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Autonomy policy — may the agent decide for itself, or must it ask?
|
|
3
|
+
* (enterprise-grade hardening, G11.)
|
|
4
|
+
*
|
|
5
|
+
* WHY THIS EXISTS (the WhatsApp story audit + the "manual cadence" complaint):
|
|
6
|
+
* Two failure modes sit on opposite sides of the same missing rule.
|
|
7
|
+
*
|
|
8
|
+
* 1. STALLING ON AN ANSWERABLE QUESTION. The story session asked/implied
|
|
9
|
+
* decisions it could have made itself ("should I keep going?", "which
|
|
10
|
+
* file?"), and every one of those turned into a turn that ended without
|
|
11
|
+
* delivered work. An agent that consults on a choice it can make is not
|
|
12
|
+
* careful — it is a bottleneck, and it is how 32 minutes produced zero
|
|
13
|
+
* words.
|
|
14
|
+
* 2. DECIDING SOMETHING THAT WAS NEVER ITS CALL. The opposite failure, where
|
|
15
|
+
* an agent silently picks a direction the user cared about (which
|
|
16
|
+
* provider to bill, whether to overwrite existing work, what to delete).
|
|
17
|
+
*
|
|
18
|
+
* The enterprise behaviour is a POLICY, not a mood: decide by default, consult
|
|
19
|
+
* only when the decision is (a) genuinely the user's, (b) not implied by the
|
|
20
|
+
* ask, (c) irreversible or expensive to get wrong, and (d) has no sensible
|
|
21
|
+
* default. Everything else is the agent's job.
|
|
22
|
+
*
|
|
23
|
+
* This module is deterministic and LLM-free on purpose: it is the guard rail
|
|
24
|
+
* around a model's own "should I ask?" instinct, which skews towards asking.
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
27
|
+
* Rework above this many minutes makes a defaulted choice expensive enough to
|
|
28
|
+
* be worth a round trip even when the choice is reversible.
|
|
29
|
+
*/
|
|
30
|
+
export const REWORK_CONSULT_THRESHOLD_MINUTES = 45;
|
|
31
|
+
/**
|
|
32
|
+
* Decide whether the agent proceeds on its own or asks the user.
|
|
33
|
+
*
|
|
34
|
+
* Order matters. The cheap, obviously-ours cases are settled first so that a
|
|
35
|
+
* later rule can never turn an easy call into a consultation:
|
|
36
|
+
*
|
|
37
|
+
* 1. NOT BLOCKING, NOT HIGH-IMPACT -> proceed. Nothing is waiting on an
|
|
38
|
+
* answer, and the user can redirect at any time. Asking is pure latency.
|
|
39
|
+
* 2. THE ASK ALREADY SAID SO -> proceed with the implied choice. Re-asking a
|
|
40
|
+
* question the user answered in their own request is the "cheap shortcut"
|
|
41
|
+
* in reverse: it looks like diligence and delivers nothing.
|
|
42
|
+
* 3. A BLOCKING, HIGH-IMPACT, IRREVERSIBLE DECISION WITH NO DEFAULT -> this is
|
|
43
|
+
* the user's call. Consult — with a defaulted recommendation, so the reply
|
|
44
|
+
* is a one-word confirmation rather than an essay.
|
|
45
|
+
* 4. OTHERWISE -> proceed with the default, unless the rework a wrong pick
|
|
46
|
+
* would cost is large enough to justify asking (rule 5).
|
|
47
|
+
* 5. EXPENSIVE + NO DEFAULT -> consult.
|
|
48
|
+
*
|
|
49
|
+
* A blocking decision with a sensible default proceeds. That is the whole
|
|
50
|
+
* point of the policy: "which file should I write to" has a default (the one
|
|
51
|
+
* named in the ask, or a predictable one), so it must never stop a run.
|
|
52
|
+
*/
|
|
53
|
+
export function decideAutonomously(request) {
|
|
54
|
+
const impact = request.impact ?? 'medium';
|
|
55
|
+
const blocking = request.blocking === true;
|
|
56
|
+
const choice = request.defaultChoice ?? request.options?.[0];
|
|
57
|
+
if (!blocking && impact !== 'high') {
|
|
58
|
+
return {
|
|
59
|
+
action: 'proceed',
|
|
60
|
+
choice,
|
|
61
|
+
reason: 'non-blocking and not high-impact — proceeding and leaving the user free to redirect',
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
if (request.impliedByAsk) {
|
|
65
|
+
return {
|
|
66
|
+
action: 'proceed',
|
|
67
|
+
choice,
|
|
68
|
+
reason: 'the request itself already specifies this — re-asking would be a round trip with no new information',
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
const irreversible = request.reversible === false;
|
|
72
|
+
const rework = request.reworkMinutes ?? 0;
|
|
73
|
+
const hasDefault = typeof choice === 'string' && choice.length > 0;
|
|
74
|
+
if (blocking && impact === 'high' && irreversible && !hasDefault) {
|
|
75
|
+
return {
|
|
76
|
+
action: 'consult',
|
|
77
|
+
reason: 'blocking, high-impact, irreversible, and no sensible default — this is genuinely the user’s decision',
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
if (hasDefault && !(irreversible && impact === 'high')) {
|
|
81
|
+
return {
|
|
82
|
+
action: 'proceed',
|
|
83
|
+
choice,
|
|
84
|
+
reason: 'a sensible default exists and the choice is recoverable — deciding it is the agent’s job',
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
if (rework > REWORK_CONSULT_THRESHOLD_MINUTES && !hasDefault) {
|
|
88
|
+
return {
|
|
89
|
+
action: 'consult',
|
|
90
|
+
reason: `a wrong pick would cost roughly ${rework} minutes and there is no default to fall back on`,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
if (!hasDefault && request.blocking) {
|
|
94
|
+
return {
|
|
95
|
+
action: 'consult',
|
|
96
|
+
reason: 'blocking, with no default the agent is willing to stand behind',
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
action: 'proceed',
|
|
101
|
+
choice,
|
|
102
|
+
reason: irreversible && impact === 'high'
|
|
103
|
+
? 'proceeding with the stated default; the choice is high-impact but the user’s own preference is already known'
|
|
104
|
+
: 'no reason to wait on an answer — proceeding',
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
// ── Write authorization: did the REQUEST already authorize this? ────────────
|
|
108
|
+
/**
|
|
109
|
+
* Verbs that mean "produce the artifact", not "tell me about it".
|
|
110
|
+
* Deliberately includes the edit verbs: "add a chapter", "update the README".
|
|
111
|
+
*/
|
|
112
|
+
const CREATE_VERB_RE = /\b(?:create|build|write|make|generate|develop|implement|scaffold|set\s+up|setup|add|produce|draft|code|rewrite|refactor|fix|update|change|edit|modify|migrate|upgrade|convert|render|export|deploy|install|save|store|persist)\b/i;
|
|
113
|
+
/** Nouns that name a FILE-SHAPED deliverable (the thing that lands on disk). */
|
|
114
|
+
const DELIVERABLE_NOUN_RE = /\b(?:file|files|folder|directory|project|repo|repository|app|application|website|site|webpage|web\s?page|page|pages|component|module|class|function|script|program|tool|cli|server|api|endpoint|test|tests|suite|config|document|doc|docs|report|readme|story|book|novel|chapter|chapters|poem|essay|article|post|plan|spec|schema|migration|template|templates|style|styles|asset|assets|manuscript|draft)\b/i;
|
|
115
|
+
/** A path is the strongest signal of all: the user named the destination. */
|
|
116
|
+
const PATH_RE = /(?:^|[\s"'`(])(?:~|\/|\.{1,2}\/)[\w./-]+/;
|
|
117
|
+
/**
|
|
118
|
+
* Directive verbs that ask for a CHANGE to something that already exists.
|
|
119
|
+
*
|
|
120
|
+
* Kept separate from {@link CREATE_VERB_RE} because the evidence is different:
|
|
121
|
+
* these need no file-shaped noun ("fix the calculator") — the verb applied to an
|
|
122
|
+
* object already says the work is to change what is there. Still vetoed by an
|
|
123
|
+
* analysis opener, so "why is the build failing?" remains a question.
|
|
124
|
+
*/
|
|
125
|
+
const MAINTENANCE_VERB_RE = /\b(?:fix|repair|refactor|update|change|edit|modify|migrate|upgrade|improve|optimize|optimise|clean\s*up|rewrite|rename|remove|delete|correct|debug|patch|tweak)\b/i;
|
|
126
|
+
/**
|
|
127
|
+
* An analysis/interrogative opener means the user is asking ABOUT something,
|
|
128
|
+
* not asking for it to be produced: "what files should I create for X?" is a
|
|
129
|
+
* question, "create the files for X" is a request.
|
|
130
|
+
*
|
|
131
|
+
* `can|could|would|do` are deliberately NOT here — "Can you create the
|
|
132
|
+
* project?" is a polite REQUEST, and treating it as analysis would leave the
|
|
133
|
+
* most common phrasing unauthorized.
|
|
134
|
+
*/
|
|
135
|
+
const ANALYSIS_OPENER_RE = /^\s*(?:how|why|what|when|which|who|whom|whose|explain|describe|summari[sz]e|compare|tell\s+me|walk\s+me|help\s+me\s+understand)\b/i;
|
|
136
|
+
/** "yes", "go ahead", "do it" — a confirmation of work already proposed. */
|
|
137
|
+
const SHORT_AFFIRMATIVE_RE = /^\s*(?:yes|yep|yeah|ok(?:ay)?|sure|please\s+do|go\s+ahead|proceed|continue|carry\s+on|do\s+it|go\s+for\s+it|make\s+it\s+so)\b/i;
|
|
138
|
+
/** Continuing in-flight work needs no new authorization — the ask was it. */
|
|
139
|
+
const CONTINUATION_RE = /\b(?:continue|carry\s+on|keep\s+going|go\s+ahead|proceed|finish|complete\s+(?:it|the|this|the\s+remaining)|do\s+the\s+remaining|resume)\b/i;
|
|
140
|
+
/**
|
|
141
|
+
* Does this request authorize the agent to CREATE files?
|
|
142
|
+
*
|
|
143
|
+
* This is the input the write gate was missing. The gate was binary — "confirm
|
|
144
|
+
* or refuse" — with no notion of a request that had ALREADY authorized the
|
|
145
|
+
* work, so an unattended run whose ask was literally "write a 12 page story"
|
|
146
|
+
* stopped to ask "Do you want me to create the files?". That is not caution;
|
|
147
|
+
* it is the manual cadence, and over a chat surface it is a round trip that
|
|
148
|
+
* produces nothing.
|
|
149
|
+
*
|
|
150
|
+
* Evidence order: an explicit confirmation of proposed work, then a
|
|
151
|
+
* continuation, then a named destination path, then a creation verb applied to
|
|
152
|
+
* a file-shaped deliverable. An analysis/interrogative opener vetoes the last
|
|
153
|
+
* two, so "explain how to write a story to a file" stays a question.
|
|
154
|
+
*
|
|
155
|
+
* Deliberately conservative: anything it does not recognise is NOT authorized,
|
|
156
|
+
* which keeps today's confirm-or-refuse gate exactly as strict as it was.
|
|
157
|
+
*/
|
|
158
|
+
export function requestAuthorizesWrites(request) {
|
|
159
|
+
const text = (request || '').trim();
|
|
160
|
+
if (!text)
|
|
161
|
+
return { authorized: false, reason: 'no request text to judge' };
|
|
162
|
+
if (text.length <= 40 && SHORT_AFFIRMATIVE_RE.test(text)) {
|
|
163
|
+
return { authorized: true, reason: 'the user confirmed the work already proposed' };
|
|
164
|
+
}
|
|
165
|
+
if (CONTINUATION_RE.test(text)) {
|
|
166
|
+
return { authorized: true, reason: 'the request continues work that was already authorized' };
|
|
167
|
+
}
|
|
168
|
+
const analyzing = ANALYSIS_OPENER_RE.test(text);
|
|
169
|
+
if (PATH_RE.test(text) && CREATE_VERB_RE.test(text) && !analyzing) {
|
|
170
|
+
return { authorized: true, reason: 'the request names a destination path for the work' };
|
|
171
|
+
}
|
|
172
|
+
if (analyzing) {
|
|
173
|
+
return { authorized: false, reason: 'the request asks ABOUT the work rather than for it to be produced' };
|
|
174
|
+
}
|
|
175
|
+
if (CREATE_VERB_RE.test(text) && DELIVERABLE_NOUN_RE.test(text)) {
|
|
176
|
+
return { authorized: true, reason: 'the request asks for a file-shaped deliverable' };
|
|
177
|
+
}
|
|
178
|
+
// The noun list names ARTIFACT TYPES (file, app, story, script…), so it misses
|
|
179
|
+
// the most common dev ask there is: "fix the calculator" names no artifact at
|
|
180
|
+
// all, and this verdict came back NOT authorized — which meant the edit gate
|
|
181
|
+
// would still stop to ask for permission to fix the thing the user asked to
|
|
182
|
+
// have fixed. A directive verb is its own evidence: it asks for a change to
|
|
183
|
+
// work that already exists.
|
|
184
|
+
if (MAINTENANCE_VERB_RE.test(text)) {
|
|
185
|
+
return { authorized: true, reason: 'the request directs a change to work that already exists' };
|
|
186
|
+
}
|
|
187
|
+
return { authorized: false, reason: 'the request does not ask for files to be created' };
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* A request that asks for the work to be recorded in git history.
|
|
191
|
+
*
|
|
192
|
+
* `git commit` was gated behind `confirm:true` "after the user approved via
|
|
193
|
+
* ask_user" — but when the user's ask IS "commit these changes", they have
|
|
194
|
+
* already approved and the gate is a round trip in the most common dev flow.
|
|
195
|
+
* Like every other rule here, the evidence has to come from the request text.
|
|
196
|
+
*
|
|
197
|
+
* A NEGATED commit ("don't commit yet") is explicitly not a request for one,
|
|
198
|
+
* and an analysis opener ("how do I commit?") is asking ABOUT it.
|
|
199
|
+
*/
|
|
200
|
+
export function requestRequestsCommit(request) {
|
|
201
|
+
const text = (request || '').trim();
|
|
202
|
+
if (!text)
|
|
203
|
+
return false;
|
|
204
|
+
if (ANALYSIS_OPENER_RE.test(text))
|
|
205
|
+
return false;
|
|
206
|
+
if (/\b(?:don'?t|do\s+not|never|no|without)\s+(?:\w+\s+){0,2}commit\b/i.test(text))
|
|
207
|
+
return false;
|
|
208
|
+
return /\b(?:commit|check[\s-]?in)\b/i.test(text);
|
|
209
|
+
}
|
|
210
|
+
/** Escape a literal for use inside a RegExp. */
|
|
211
|
+
function escapeRegexLiteral(text) {
|
|
212
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Does the request name THIS file?
|
|
216
|
+
*
|
|
217
|
+
* The evidence an edit gate needs: "fix calc.ts" names the file, "update the
|
|
218
|
+
* README" names it. Compared on the BASENAME too, because a request says
|
|
219
|
+
* `calc.ts` for `src/lib/calc.ts` — that is how people actually write, and
|
|
220
|
+
* requiring the full path would make the rule dead on arrival.
|
|
221
|
+
*
|
|
222
|
+
* Deliberately literal (the basename or the path, as a whole word): a fuzzy
|
|
223
|
+
* match would let "write a story" claim `story.md` and turn a create into an
|
|
224
|
+
* unreviewed overwrite.
|
|
225
|
+
*/
|
|
226
|
+
export function requestNamesPath(request, path) {
|
|
227
|
+
const text = (request || '').trim();
|
|
228
|
+
if (!text)
|
|
229
|
+
return false;
|
|
230
|
+
const base = path.split(/[\\/]/).pop() ?? '';
|
|
231
|
+
const candidates = [base, path].filter((c) => c.length >= 3);
|
|
232
|
+
return candidates.some((c) => new RegExp(`(?:^|[^\\w.\\/-])${escapeRegexLiteral(c)}(?:$|[^\\w.\\/-])`, 'i').test(text));
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Above this share of a file being rewritten, an edit is a REPLACEMENT rather
|
|
236
|
+
* than a surgical change — and replacing most of what exists is the user's call,
|
|
237
|
+
* for the same reason write_file's overwrite case is.
|
|
238
|
+
*/
|
|
239
|
+
export const EDIT_SURGICAL_MAX_FRACTION = 0.5;
|
|
240
|
+
/**
|
|
241
|
+
* Is this edit surgical — does it preserve most of the file it touches?
|
|
242
|
+
*
|
|
243
|
+
* The measurable line between "a fix inside a file" and "a rewrite of the
|
|
244
|
+
* file". A surgical edit is the iteration the verify loop is built around
|
|
245
|
+
* (run → read the failure → edit → re-run); a rewrite is a judgment call.
|
|
246
|
+
*/
|
|
247
|
+
export function isSurgicalEdit(fileChars, oldChars, newChars) {
|
|
248
|
+
if (!Number.isFinite(fileChars) || fileChars <= 0)
|
|
249
|
+
return false;
|
|
250
|
+
const touched = Math.max(oldChars, newChars);
|
|
251
|
+
return touched / fileChars <= EDIT_SURGICAL_MAX_FRACTION;
|
|
252
|
+
}
|
|
253
|
+
// ── Permission-seeking detection ────────────────────────────────────────────
|
|
254
|
+
/** Direct permission requests: "do you want me to …", "shall I …". */
|
|
255
|
+
const PERMISSION_ASK_RE = /\b(?:do\s+you\s+want\s+(?:me|for\s+me)\s+to|would\s+you\s+like\s+(?:me|for\s+me)\s+to|want\s+me\s+to|like\s+me\s+to|shall\s+i|should\s+i|may\s+i|am\s+i\s+allowed\s+to|is\s+it\s+ok(?:ay)?\s+(?:if|to)|do\s+you\s+want\s+me\s+to|ready\s+to\s+proceed|ok(?:ay)?\s+to\s+proceed)\b/i;
|
|
256
|
+
/** Awaiting-a-human phrasings: "awaiting your confirmation", "let me know if…". */
|
|
257
|
+
const PERMISSION_AWAIT_RE = /\b(?:awaiting|waiting\s+for|need)\s+(?:your|the\s+user'?s?)\s+(?:approval|confirmation|go-?ahead|permission|sign-?off|consent|green\s*light)\b|\b(?:your|the\s+user'?s?)\s+(?:approval|confirmation|go-?ahead|permission|sign-?off)\b|\b(?:if|once|when)\s+you\s+(?:approve|confirm|give\s+me\s+the\s+(?:go-?ahead|green\s*light|ok))\b|\blet\s+me\s+know\s+(?:if|whether)\s+you(?:'d|\s+would)?\s+(?:like|want)\b|\bplease\s+(?:confirm|approve)\b/i;
|
|
258
|
+
/**
|
|
259
|
+
* A question that PROPOSES doing the work rather than asking about it —
|
|
260
|
+
* "Create the Mahagatha interactive-book project with 20 chapter pages…?"
|
|
261
|
+
* (the exact shape that ended a live unattended turn).
|
|
262
|
+
*/
|
|
263
|
+
const PROPOSAL_QUESTION_RE = /\?\s*$/;
|
|
264
|
+
/** Sentence split that keeps the shape of the original text for callers that
|
|
265
|
+
* need to CUT rather than merely match. */
|
|
266
|
+
function splitSentences(text) {
|
|
267
|
+
return text
|
|
268
|
+
.split(/(?<=[.!?。!?])\s+|\n+/)
|
|
269
|
+
.map((s) => s.trim())
|
|
270
|
+
.filter(Boolean);
|
|
271
|
+
}
|
|
272
|
+
/** Is this ONE sentence a request for permission / a proposal to do the work? */
|
|
273
|
+
export function isPermissionSeekingSentence(sentence) {
|
|
274
|
+
const s = (sentence || '').trim();
|
|
275
|
+
if (!s)
|
|
276
|
+
return false;
|
|
277
|
+
if (PERMISSION_ASK_RE.test(s) || PERMISSION_AWAIT_RE.test(s))
|
|
278
|
+
return true;
|
|
279
|
+
// A question that proposes CREATE_VERB + DELIVERABLE is asking WHETHER to do
|
|
280
|
+
// the work, not asking about it (the "Create the Mahagatha project…?" shape).
|
|
281
|
+
return PROPOSAL_QUESTION_RE.test(s) && CREATE_VERB_RE.test(s) && DELIVERABLE_NOUN_RE.test(s);
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* True when the answer CLOSES by asking permission to do work, rather than
|
|
285
|
+
* delivering it.
|
|
286
|
+
*
|
|
287
|
+
* The distinction that matters: "should I proceed?" is a stall, while "which
|
|
288
|
+
* title do you prefer?" is a genuine question. This detector only claims the
|
|
289
|
+
* permission-seeking half — callers AND it with an authorization verdict, so a
|
|
290
|
+
* question about work that was never authorized is untouched.
|
|
291
|
+
*
|
|
292
|
+
* Only the closing sentences are judged: a question mid-answer followed by the
|
|
293
|
+
* actual deliverable is narration, not a stall.
|
|
294
|
+
*/
|
|
295
|
+
export function detectPermissionSeeking(content) {
|
|
296
|
+
const text = (content || '').trim();
|
|
297
|
+
if (!text)
|
|
298
|
+
return false;
|
|
299
|
+
return splitSentences(text).slice(-2).some(isPermissionSeekingSentence);
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Remove the trailing permission-seeking sentences from an answer, keeping
|
|
303
|
+
* everything before them VERBATIM (deliverables are prose — re-joining split
|
|
304
|
+
* sentences would reformat the work itself).
|
|
305
|
+
*
|
|
306
|
+
* Used when a turn is nudged to proceed: the real work in that step must stay
|
|
307
|
+
* a candidate answer, but the question around it must not be what the user is
|
|
308
|
+
* handed — otherwise a longer "…Do you want me to…?" outranks a shorter, real
|
|
309
|
+
* follow-up answer under the loop's longest-substantive rule.
|
|
310
|
+
*/
|
|
311
|
+
export function stripTrailingPermissionSeek(content) {
|
|
312
|
+
let out = (content || '').trim();
|
|
313
|
+
// Two sentences is the detection window — never cut deeper than it can see.
|
|
314
|
+
for (let i = 0; i < 2; i += 1) {
|
|
315
|
+
const sentences = splitSentences(out);
|
|
316
|
+
const last = sentences[sentences.length - 1];
|
|
317
|
+
if (!last || !isPermissionSeekingSentence(last))
|
|
318
|
+
break;
|
|
319
|
+
const at = out.lastIndexOf(last);
|
|
320
|
+
if (at <= 0)
|
|
321
|
+
return '';
|
|
322
|
+
out = out.slice(0, at).trim();
|
|
323
|
+
}
|
|
324
|
+
return out;
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Actions that cannot be undone by re-running the agent. A question that names
|
|
328
|
+
* one of these is NEVER treated as reflexive permission-seeking, because the
|
|
329
|
+
* user genuinely has to own it.
|
|
330
|
+
*/
|
|
331
|
+
export const IRREVERSIBLE_ACTION_RE = /\b(?:delete|remove|erase|overwrite|replace\s+(?:the\s+)?existing|publish|deploy|push|force|reset|drop|truncate|uninstall|wipe|send|email|charge|pay|bill|refund|order|purchase)\b/i;
|
|
332
|
+
/** Decide whether a state-changing write proceeds or asks the user. */
|
|
333
|
+
export function decideWriteConfirmation(request) {
|
|
334
|
+
if (!request.authorizedByRequest) {
|
|
335
|
+
return {
|
|
336
|
+
action: 'ask',
|
|
337
|
+
reason: 'the request did not ask for files to be created, so this write is not the user’s stated intent',
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
if (request.exists) {
|
|
341
|
+
return {
|
|
342
|
+
action: 'ask',
|
|
343
|
+
reason: `'${request.path}' already exists — replacing existing content cannot be undone by re-running, so it is the user’s call`,
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
return {
|
|
347
|
+
action: 'proceed',
|
|
348
|
+
reason: 'the request asked for this file and nothing exists at that path yet — creating it destroys nothing and is undone by deleting it, so a round trip would be the manual cadence',
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Decide whether a state-changing action proceeds or asks the user.
|
|
353
|
+
*
|
|
354
|
+
* Order matters, and the two never-autonomous classes are settled first so no
|
|
355
|
+
* later rule can turn them into an autonomous action:
|
|
356
|
+
*
|
|
357
|
+
* 1. DESTRUCTIVE -> ask. Permanent, so it stays the user's call even when
|
|
358
|
+
* they named it: one round trip is cheap, being wrong is not.
|
|
359
|
+
* 2. EXTERNAL -> ask. Someone else sees it, or it is billed.
|
|
360
|
+
* 3. NEITHER AUTHORIZED NOR NAMED -> ask. This is the original strictness
|
|
361
|
+
* (and the no-loop-context default) untouched.
|
|
362
|
+
* 4. NAMED BY THE REQUEST -> proceed. The user's own words are the
|
|
363
|
+
* authorization; re-asking is a round trip with no new information.
|
|
364
|
+
* 5. AUTHORIZED + RECOVERABLE -> proceed. The work was ordered and the change
|
|
365
|
+
* cannot strand the user.
|
|
366
|
+
* 6. OTHERWISE -> ask. Authorized but neither named nor measurably
|
|
367
|
+
* recoverable is exactly the case where a surprise is possible.
|
|
368
|
+
*/
|
|
369
|
+
export function decideStateChange(request) {
|
|
370
|
+
if (request.changeClass === 'destructive') {
|
|
371
|
+
return {
|
|
372
|
+
action: 'ask',
|
|
373
|
+
reason: `${request.action} removes or irrecoverably replaces content that already exists — that is permanent, so it stays the user’s call`,
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
if (request.changeClass === 'external') {
|
|
377
|
+
return {
|
|
378
|
+
action: 'ask',
|
|
379
|
+
reason: `${request.action} has an effect outside this machine (visible to others, billed, or unrecoverable) — the user owns that decision`,
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
const named = request.namedByRequest === true;
|
|
383
|
+
if (request.authorizedByRequest !== true && !named) {
|
|
384
|
+
return {
|
|
385
|
+
action: 'ask',
|
|
386
|
+
reason: 'the request did not ask for this work, so carrying it out would be a surprise',
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
if (named) {
|
|
390
|
+
return {
|
|
391
|
+
action: 'proceed',
|
|
392
|
+
reason: 'the request itself names this action — asking again would be a round trip with no new information',
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
if (request.recoverable === true) {
|
|
396
|
+
return {
|
|
397
|
+
action: 'proceed',
|
|
398
|
+
reason: 'the request asked for this work and the change is recoverable, so deciding it is the agent’s job',
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
return {
|
|
402
|
+
action: 'ask',
|
|
403
|
+
reason: `${request.action} replaces too much of what already exists to assume it — a re-run cannot recover it, so it is the user’s call`,
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
// ── run_cli: which gated intents may be decided autonomously ───────────────
|
|
407
|
+
/**
|
|
408
|
+
* Confirmation-gated CLI intents that cannot be undone by re-running the
|
|
409
|
+
* agent: irreversible data loss, or an effect outside this machine.
|
|
410
|
+
*
|
|
411
|
+
* These keep their gate even when the request names them. One round trip is a
|
|
412
|
+
* small price for a decision that cannot be taken back.
|
|
413
|
+
*/
|
|
414
|
+
export const IRREVERSIBLE_CLI_INTENTS = new Set([
|
|
415
|
+
'history.clear',
|
|
416
|
+
'memory.prune',
|
|
417
|
+
'stats.cost.clear',
|
|
418
|
+
'publish',
|
|
419
|
+
]);
|
|
420
|
+
/**
|
|
421
|
+
* Confirmation-gated CLI intents that ARE recoverable: a service that can be
|
|
422
|
+
* started again, config that can be re-added, a cache that rebuilds, a skill
|
|
423
|
+
* that reinstalls.
|
|
424
|
+
*
|
|
425
|
+
* These proceed when the user's own request resolves to the exact command. The
|
|
426
|
+
* manifest flag exists because the ACTION is stateful, not because the user
|
|
427
|
+
* needs to approve what they just asked for.
|
|
428
|
+
*/
|
|
429
|
+
export const RECOVERABLE_CLI_INTENTS = new Set([
|
|
430
|
+
'dashboard.stop',
|
|
431
|
+
'gateway.stop',
|
|
432
|
+
'health.selfheal',
|
|
433
|
+
'memory.optimize',
|
|
434
|
+
'cache.clear',
|
|
435
|
+
'skills.uninstall',
|
|
436
|
+
'contacts.remove',
|
|
437
|
+
'permissions.disallow',
|
|
438
|
+
'platform.remove',
|
|
439
|
+
'cron.remove',
|
|
440
|
+
]);
|
|
441
|
+
/**
|
|
442
|
+
* Decide a confirmation-gated CLI intent.
|
|
443
|
+
*
|
|
444
|
+
* Note what this does NOT consult: {@link requestAuthorizesWrites}. That verdict
|
|
445
|
+
* answers "did the request ask for files to be created" — the wrong lens for a
|
|
446
|
+
* system ask like "stop the dashboard", which would read as unauthorized and
|
|
447
|
+
* defeat the whole rule. The evidence that matters here is whether the USER'S
|
|
448
|
+
* OWN WORDS resolve to this exact command (the caller resolves them with the
|
|
449
|
+
* same router, so the tool's ask and the user's ask are compared like for like).
|
|
450
|
+
*/
|
|
451
|
+
export function decideCliIntentConfirmation(request) {
|
|
452
|
+
if (IRREVERSIBLE_CLI_INTENTS.has(request.intent)) {
|
|
453
|
+
return {
|
|
454
|
+
action: 'ask',
|
|
455
|
+
reason: `'${request.intent}' cannot be undone by re-running — one round trip is cheap, and being wrong is not`,
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
if (RECOVERABLE_CLI_INTENTS.has(request.intent)) {
|
|
459
|
+
return request.namedByRequest
|
|
460
|
+
? {
|
|
461
|
+
action: 'proceed',
|
|
462
|
+
reason: `the user's own request resolves to '${request.intent}' — asking them to confirm what they just asked for is the manual cadence`,
|
|
463
|
+
}
|
|
464
|
+
: {
|
|
465
|
+
action: 'ask',
|
|
466
|
+
reason: `'${request.intent}' was not what the user asked for — it is the agent's own initiative, so it is their call`,
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
return {
|
|
470
|
+
action: 'ask',
|
|
471
|
+
reason: `'${request.intent}' is confirmation-gated and not classified as recoverable — asking`,
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* The line shown when the agent decides something on the user's behalf.
|
|
476
|
+
*
|
|
477
|
+
* Reported, never silent: the user must be able to see and reverse a judgment
|
|
478
|
+
* call. A decision that is invisible is indistinguishable from a bug.
|
|
479
|
+
*/
|
|
480
|
+
export function autonomouslyDecidedLine(question, choice, reason) {
|
|
481
|
+
return `🤖 Decided without asking — ${question} → **${choice}** (${reason}). Say the word and I'll change it.`;
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* The line shown when the agent genuinely cannot decide for the user.
|
|
485
|
+
*
|
|
486
|
+
* Deliberately shaped for a one-word reply: the recommendation and the
|
|
487
|
+
* fallback are both named, so the user is confirming rather than composing.
|
|
488
|
+
*/
|
|
489
|
+
export function consultLine(request, verdict) {
|
|
490
|
+
const options = (request.options ?? []).slice(0, 4);
|
|
491
|
+
const parts = [`❓ I need your call on this: ${request.question}`, `_${verdict.reason}_`];
|
|
492
|
+
if (options.length > 1) {
|
|
493
|
+
parts.push('', 'Options:', ...options.map((o, i) => `${i + 1}. ${o}`));
|
|
494
|
+
}
|
|
495
|
+
if (request.defaultChoice) {
|
|
496
|
+
parts.push('', `My recommendation: **${request.defaultChoice}** — say "go" and I'll take it.`);
|
|
497
|
+
}
|
|
498
|
+
return parts.join('\n');
|
|
499
|
+
}
|
|
500
|
+
//# sourceMappingURL=autonomy-policy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"autonomy-policy.js","sourceRoot":"","sources":["../../src/learning/autonomy-policy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAgDH;;;GAGG;AACH,MAAM,CAAC,MAAM,gCAAgC,GAAG,EAAE,CAAC;AAEnD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAAwB;IACzD,MAAM,MAAM,GAAmB,OAAO,CAAC,MAAM,IAAI,QAAQ,CAAC;IAC1D,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,KAAK,IAAI,CAAC;IAC3C,MAAM,MAAM,GAAG,OAAO,CAAC,aAAa,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC;IAE7D,IAAI,CAAC,QAAQ,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;QACnC,OAAO;YACL,MAAM,EAAE,SAAS;YACjB,MAAM;YACN,MAAM,EAAE,qFAAqF;SAC9F,CAAC;IACJ,CAAC;IAED,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC;QACzB,OAAO;YACL,MAAM,EAAE,SAAS;YACjB,MAAM;YACN,MAAM,EAAE,qGAAqG;SAC9G,CAAC;IACJ,CAAC;IAED,MAAM,YAAY,GAAG,OAAO,CAAC,UAAU,KAAK,KAAK,CAAC;IAClD,MAAM,MAAM,GAAG,OAAO,CAAC,aAAa,IAAI,CAAC,CAAC;IAC1C,MAAM,UAAU,GAAG,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;IAEnE,IAAI,QAAQ,IAAI,MAAM,KAAK,MAAM,IAAI,YAAY,IAAI,CAAC,UAAU,EAAE,CAAC;QACjE,OAAO;YACL,MAAM,EAAE,SAAS;YACjB,MAAM,EAAE,sGAAsG;SAC/G,CAAC;IACJ,CAAC;IAED,IAAI,UAAU,IAAI,CAAC,CAAC,YAAY,IAAI,MAAM,KAAK,MAAM,CAAC,EAAE,CAAC;QACvD,OAAO;YACL,MAAM,EAAE,SAAS;YACjB,MAAM;YACN,MAAM,EAAE,0FAA0F;SACnG,CAAC;IACJ,CAAC;IAED,IAAI,MAAM,GAAG,gCAAgC,IAAI,CAAC,UAAU,EAAE,CAAC;QAC7D,OAAO;YACL,MAAM,EAAE,SAAS;YACjB,MAAM,EAAE,mCAAmC,MAAM,kDAAkD;SACpG,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,UAAU,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;QACpC,OAAO;YACL,MAAM,EAAE,SAAS;YACjB,MAAM,EAAE,gEAAgE;SACzE,CAAC;IACJ,CAAC;IAED,OAAO;QACL,MAAM,EAAE,SAAS;QACjB,MAAM;QACN,MAAM,EAAE,YAAY,IAAI,MAAM,KAAK,MAAM;YACvC,CAAC,CAAC,8GAA8G;YAChH,CAAC,CAAC,6CAA6C;KAClD,CAAC;AACJ,CAAC;AAED,+EAA+E;AAE/E;;;GAGG;AACH,MAAM,cAAc,GAClB,mOAAmO,CAAC;AAEtO,gFAAgF;AAChF,MAAM,mBAAmB,GACvB,gZAAgZ,CAAC;AAEnZ,6EAA6E;AAC7E,MAAM,OAAO,GAAG,0CAA0C,CAAC;AAE3D;;;;;;;GAOG;AACH,MAAM,mBAAmB,GACvB,oKAAoK,CAAC;AAEvK;;;;;;;;GAQG;AACH,MAAM,kBAAkB,GACtB,oIAAoI,CAAC;AAEvI,4EAA4E;AAC5E,MAAM,oBAAoB,GACxB,gIAAgI,CAAC;AAEnI,6EAA6E;AAC7E,MAAM,eAAe,GACnB,4IAA4I,CAAC;AAS/I;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,uBAAuB,CAAC,OAAe;IACrD,MAAM,IAAI,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACpC,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,0BAA0B,EAAE,CAAC;IAE5E,IAAI,IAAI,CAAC,MAAM,IAAI,EAAE,IAAI,oBAAoB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACzD,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,8CAA8C,EAAE,CAAC;IACtF,CAAC;IACD,IAAI,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAC/B,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,wDAAwD,EAAE,CAAC;IAChG,CAAC;IAED,MAAM,SAAS,GAAG,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAChD,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;QAClE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,mDAAmD,EAAE,CAAC;IAC3F,CAAC;IACD,IAAI,SAAS,EAAE,CAAC;QACd,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,mEAAmE,EAAE,CAAC;IAC5G,CAAC;IACD,IAAI,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAChE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,gDAAgD,EAAE,CAAC;IACxF,CAAC;IACD,+EAA+E;IAC/E,8EAA8E;IAC9E,6EAA6E;IAC7E,4EAA4E;IAC5E,4EAA4E;IAC5E,4BAA4B;IAC5B,IAAI,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACnC,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,0DAA0D,EAAE,CAAC;IAClG,CAAC;IACD,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,kDAAkD,EAAE,CAAC;AAC3F,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,qBAAqB,CAAC,OAAe;IACnD,MAAM,IAAI,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACpC,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAC;IACxB,IAAI,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IAChD,IAAI,mEAAmE,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IACjG,OAAO,+BAA+B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACpD,CAAC;AAED,gDAAgD;AAChD,SAAS,kBAAkB,CAAC,IAAY;IACtC,OAAO,IAAI,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC;AACrD,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAAe,EAAE,IAAY;IAC5D,MAAM,IAAI,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACpC,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAC;IACxB,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC;IAC7C,MAAM,UAAU,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC;IAC7D,OAAO,UAAU,CAAC,IAAI,CACpB,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,MAAM,CAAC,oBAAoB,kBAAkB,CAAC,CAAC,CAAC,mBAAmB,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAChG,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,GAAG,CAAC;AAE9C;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,SAAiB,EAAE,QAAgB,EAAE,QAAgB;IAClF,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,SAAS,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IAChE,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAC7C,OAAO,OAAO,GAAG,SAAS,IAAI,0BAA0B,CAAC;AAC3D,CAAC;AAED,+EAA+E;AAE/E,sEAAsE;AACtE,MAAM,iBAAiB,GACrB,oRAAoR,CAAC;AAEvR,mFAAmF;AACnF,MAAM,mBAAmB,GACvB,sbAAsb,CAAC;AAEzb;;;;GAIG;AACH,MAAM,oBAAoB,GAAG,QAAQ,CAAC;AAEtC;2CAC2C;AAC3C,SAAS,cAAc,CAAC,IAAY;IAClC,OAAO,IAAI;SACR,KAAK,CAAC,sBAAsB,CAAC;SAC7B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,MAAM,CAAC,OAAO,CAAC,CAAC;AACrB,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,2BAA2B,CAAC,QAAgB;IAC1D,MAAM,CAAC,GAAG,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAClC,IAAI,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IACrB,IAAI,iBAAiB,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,mBAAmB,CAAC,IAAI,CAAC,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAC1E,6EAA6E;IAC7E,8EAA8E;IAC9E,OAAO,oBAAoB,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,mBAAmB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAC/F,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,uBAAuB,CAAC,OAAe;IACrD,MAAM,IAAI,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACpC,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAC;IACxB,OAAO,cAAc,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,2BAA2B,CAAC,CAAC;AAC1E,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,2BAA2B,CAAC,OAAe;IACzD,IAAI,GAAG,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACjC,4EAA4E;IAC5E,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QAC9B,MAAM,SAAS,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC;QACtC,MAAM,IAAI,GAAG,SAAS,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QAC7C,IAAI,CAAC,IAAI,IAAI,CAAC,2BAA2B,CAAC,IAAI,CAAC;YAAE,MAAM;QACvD,MAAM,EAAE,GAAG,GAAG,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;QACjC,IAAI,EAAE,IAAI,CAAC;YAAE,OAAO,EAAE,CAAC;QACvB,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAChC,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,sBAAsB,GACjC,oLAAoL,CAAC;AAmCvL,uEAAuE;AACvE,MAAM,UAAU,uBAAuB,CAAC,OAAiC;IACvE,IAAI,CAAC,OAAO,CAAC,mBAAmB,EAAE,CAAC;QACjC,OAAO;YACL,MAAM,EAAE,KAAK;YACb,MAAM,EAAE,gGAAgG;SACzG,CAAC;IACJ,CAAC;IACD,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;QACnB,OAAO;YACL,MAAM,EAAE,KAAK;YACb,MAAM,EAAE,IAAI,OAAO,CAAC,IAAI,wGAAwG;SACjI,CAAC;IACJ,CAAC;IACD,OAAO;QACL,MAAM,EAAE,SAAS;QACjB,MAAM,EAAE,8KAA8K;KACvL,CAAC;AACJ,CAAC;AAgDD;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAA2B;IAC3D,IAAI,OAAO,CAAC,WAAW,KAAK,aAAa,EAAE,CAAC;QAC1C,OAAO;YACL,MAAM,EAAE,KAAK;YACb,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,iHAAiH;SAC3I,CAAC;IACJ,CAAC;IACD,IAAI,OAAO,CAAC,WAAW,KAAK,UAAU,EAAE,CAAC;QACvC,OAAO;YACL,MAAM,EAAE,KAAK;YACb,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,iHAAiH;SAC3I,CAAC;IACJ,CAAC;IACD,MAAM,KAAK,GAAG,OAAO,CAAC,cAAc,KAAK,IAAI,CAAC;IAC9C,IAAI,OAAO,CAAC,mBAAmB,KAAK,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;QACnD,OAAO;YACL,MAAM,EAAE,KAAK;YACb,MAAM,EAAE,+EAA+E;SACxF,CAAC;IACJ,CAAC;IACD,IAAI,KAAK,EAAE,CAAC;QACV,OAAO;YACL,MAAM,EAAE,SAAS;YACjB,MAAM,EAAE,mGAAmG;SAC5G,CAAC;IACJ,CAAC;IACD,IAAI,OAAO,CAAC,WAAW,KAAK,IAAI,EAAE,CAAC;QACjC,OAAO;YACL,MAAM,EAAE,SAAS;YACjB,MAAM,EAAE,kGAAkG;SAC3G,CAAC;IACJ,CAAC;IACD,OAAO;QACL,MAAM,EAAE,KAAK;QACb,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,+GAA+G;KACzI,CAAC;AACJ,CAAC;AAED,8EAA8E;AAE9E;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAwB,IAAI,GAAG,CAAC;IACnE,eAAe;IACf,cAAc;IACd,kBAAkB;IAClB,SAAS;CACV,CAAC,CAAC;AAEH;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAwB,IAAI,GAAG,CAAC;IAClE,gBAAgB;IAChB,cAAc;IACd,iBAAiB;IACjB,iBAAiB;IACjB,aAAa;IACb,kBAAkB;IAClB,iBAAiB;IACjB,sBAAsB;IACtB,iBAAiB;IACjB,aAAa;CACd,CAAC,CAAC;AAEH;;;;;;;;;GASG;AACH,MAAM,UAAU,2BAA2B,CAAC,OAI3C;IACC,IAAI,wBAAwB,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QACjD,OAAO;YACL,MAAM,EAAE,KAAK;YACb,MAAM,EAAE,IAAI,OAAO,CAAC,MAAM,oFAAoF;SAC/G,CAAC;IACJ,CAAC;IACD,IAAI,uBAAuB,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAChD,OAAO,OAAO,CAAC,cAAc;YAC3B,CAAC,CAAC;gBACE,MAAM,EAAE,SAAS;gBACjB,MAAM,EAAE,uCAAuC,OAAO,CAAC,MAAM,2EAA2E;aACzI;YACH,CAAC,CAAC;gBACE,MAAM,EAAE,KAAK;gBACb,MAAM,EAAE,IAAI,OAAO,CAAC,MAAM,2FAA2F;aACtH,CAAC;IACR,CAAC;IACD,OAAO;QACL,MAAM,EAAE,KAAK;QACb,MAAM,EAAE,IAAI,OAAO,CAAC,MAAM,oEAAoE;KAC/F,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CAAC,QAAgB,EAAE,MAAc,EAAE,MAAc;IACtF,OAAO,+BAA+B,QAAQ,QAAQ,MAAM,OAAO,MAAM,qCAAqC,CAAC;AACjH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,OAAwB,EAAE,OAAwB;IAC5E,MAAM,OAAO,GAAG,CAAC,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACpD,MAAM,KAAK,GAAG,CAAC,+BAA+B,OAAO,CAAC,QAAQ,EAAE,EAAE,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;IACzF,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvB,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;IACzE,CAAC;IACD,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC;QAC1B,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,wBAAwB,OAAO,CAAC,aAAa,iCAAiC,CAAC,CAAC;IACjG,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential Fingerprint — notice when the user's keys change, and re-probe.
|
|
3
|
+
*
|
|
4
|
+
* WHY THIS EXISTS. The routing pool can only contain models a probe has
|
|
5
|
+
* VERIFIED, and a probe only ever ran on a cold start or a maintenance command.
|
|
6
|
+
* So a purchase made mid-session — the exact case where a user expects the new
|
|
7
|
+
* models to appear — changed nothing: the credential was fine, the catalog was
|
|
8
|
+
* stale, and the next run would have picked the models up only if it happened to
|
|
9
|
+
* cold-start. There is no per-model entitlement API to ask ("may I buy
|
|
10
|
+
* `meta-llama/...`?" is not a question OpenRouter answers); the only honest
|
|
11
|
+
* signal is *"we sent one token with this key and it answered"*. But it is
|
|
12
|
+
* genuinely cheap to notice that the KEY SET changed and force that probe
|
|
13
|
+
* instead of waiting for an accident.
|
|
14
|
+
*
|
|
15
|
+
* WHAT IS STORED: a SHA-256 digest of the credential SHAPE — which providers are
|
|
16
|
+
* configured, a digest of each key, each base URL, each env-var presence. Never
|
|
17
|
+
* a key, not even truncated, and the digest is one-way, so the file is safe to
|
|
18
|
+
* read, copy or commit by accident. Its only job is equality.
|
|
19
|
+
*/
|
|
20
|
+
import type { ConfigManager } from '../config/manager.js';
|
|
21
|
+
/** Sidecar file (under the memory dir) holding the last-seen credential shape. */
|
|
22
|
+
export declare const CREDENTIAL_FINGERPRINT_FILENAME = "credential-fingerprint.json";
|
|
23
|
+
/** Path of the fingerprint sidecar (exported so tests can redirect it). */
|
|
24
|
+
export declare function credentialFingerprintPath(): string;
|
|
25
|
+
/**
|
|
26
|
+
* The credential SHAPE, as a list of comparable lines.
|
|
27
|
+
*
|
|
28
|
+
* Exported (and separate from the hashing) so a test — or a support call — can
|
|
29
|
+
* see exactly which inputs are considered without needing to reverse a digest.
|
|
30
|
+
* The lines carry digests, never keys.
|
|
31
|
+
*/
|
|
32
|
+
export declare function credentialShapeInputs(configManager: ConfigManager): string[];
|
|
33
|
+
/** SHA-256 digest of the credential shape. Safe to log. */
|
|
34
|
+
export declare function credentialFingerprint(configManager: ConfigManager): string;
|
|
35
|
+
export interface CredentialChange {
|
|
36
|
+
/** True when the shape differs from the last one recorded. */
|
|
37
|
+
changed: boolean;
|
|
38
|
+
/** True when nothing had been recorded yet (no comparison was possible). */
|
|
39
|
+
firstRun: boolean;
|
|
40
|
+
/** The shape as of this call — already recorded by the time this returns. */
|
|
41
|
+
fingerprint: string;
|
|
42
|
+
/** The previously recorded shape, when there was one. */
|
|
43
|
+
previous?: string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Compare the current credential shape against the last one recorded and RECORD
|
|
47
|
+
* the new one.
|
|
48
|
+
*
|
|
49
|
+
* Recording on every call is what makes this fire once per change rather than
|
|
50
|
+
* once per cycle: the caller sees `changed: true` a single time and can force a
|
|
51
|
+
* probe, and every later call in the same process is a no-op. `firstRun` is
|
|
52
|
+
* reported separately because "no previous record" is not evidence of a change —
|
|
53
|
+
* a fresh machine must not be treated as though a purchase just landed.
|
|
54
|
+
*/
|
|
55
|
+
export declare function detectCredentialChange(configManager: ConfigManager, options?: {
|
|
56
|
+
path?: string;
|
|
57
|
+
}): CredentialChange;
|
|
58
|
+
//# sourceMappingURL=credential-fingerprint.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credential-fingerprint.d.ts","sourceRoot":"","sources":["../../src/learning/credential-fingerprint.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAQH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAE1D,kFAAkF;AAClF,eAAO,MAAM,+BAA+B,gCAAgC,CAAC;AAQ7E,2EAA2E;AAC3E,wBAAgB,yBAAyB,IAAI,MAAM,CAElD;AAOD;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,aAAa,EAAE,aAAa,GAAG,MAAM,EAAE,CAmC5E;AAED,2DAA2D;AAC3D,wBAAgB,qBAAqB,CAAC,aAAa,EAAE,aAAa,GAAG,MAAM,CAE1E;AAED,MAAM,WAAW,gBAAgB;IAC/B,8DAA8D;IAC9D,OAAO,EAAE,OAAO,CAAC;IACjB,4EAA4E;IAC5E,QAAQ,EAAE,OAAO,CAAC;IAClB,6EAA6E;IAC7E,WAAW,EAAE,MAAM,CAAC;IACpB,yDAAyD;IACzD,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CACpC,aAAa,EAAE,aAAa,EAC5B,OAAO,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAO,GAC9B,gBAAgB,CA4BlB"}
|