@controlvector/cv-agent 1.11.1 → 1.13.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/LICENSE +201 -0
- package/NOTICE +2 -0
- package/README.md +12 -2
- package/dist/bundle.cjs +17667 -339
- package/dist/bundle.cjs.map +4 -4
- package/dist/commands/agent-git.d.ts +1 -0
- package/dist/commands/agent-git.d.ts.map +1 -1
- package/dist/commands/agent-git.js +26 -3
- package/dist/commands/agent-git.js.map +1 -1
- package/dist/commands/agent.d.ts +49 -0
- package/dist/commands/agent.d.ts.map +1 -1
- package/dist/commands/agent.js +164 -10
- package/dist/commands/agent.js.map +1 -1
- package/dist/commands/auth.d.ts +16 -0
- package/dist/commands/auth.d.ts.map +1 -1
- package/dist/commands/auth.js +106 -1
- package/dist/commands/auth.js.map +1 -1
- package/dist/commands/git-credential.d.ts +14 -0
- package/dist/commands/git-credential.d.ts.map +1 -0
- package/dist/commands/git-credential.js +53 -0
- package/dist/commands/git-credential.js.map +1 -0
- package/dist/commands/setup.d.ts +20 -0
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +44 -23
- package/dist/commands/setup.js.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/utils/api.d.ts +1 -1
- package/dist/utils/api.d.ts.map +1 -1
- package/dist/utils/api.js +4 -2
- package/dist/utils/api.js.map +1 -1
- package/dist/utils/config.d.ts +5 -0
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/credentials.d.ts +2 -0
- package/dist/utils/credentials.d.ts.map +1 -1
- package/dist/utils/credentials.js.map +1 -1
- package/dist/utils/git-credentials.d.ts +78 -0
- package/dist/utils/git-credentials.d.ts.map +1 -0
- package/dist/utils/git-credentials.js +171 -0
- package/dist/utils/git-credentials.js.map +1 -0
- package/dist/utils/meter.d.ts +59 -0
- package/dist/utils/meter.d.ts.map +1 -0
- package/dist/utils/meter.js +125 -0
- package/dist/utils/meter.js.map +1 -0
- package/dist/utils/profile.d.ts +123 -0
- package/dist/utils/profile.d.ts.map +1 -0
- package/dist/utils/profile.js +266 -0
- package/dist/utils/profile.js.map +1 -0
- package/package.json +18 -8
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Git credential handling for the cv-hub git host.
|
|
4
|
+
*
|
|
5
|
+
* The cv-hub REST API accepts a session/API token that is a JWT. The cv-hub
|
|
6
|
+
* GIT host does NOT accept that JWT for push; only a minted cv_pat (an opaque
|
|
7
|
+
* personal access token) authenticates. cv-agent onboarding historically wrote
|
|
8
|
+
* the JWT into ~/.git-credentials, so non-interactive pushes failed.
|
|
9
|
+
*
|
|
10
|
+
* This module distinguishes the two token kinds, selects the git-capable one,
|
|
11
|
+
* derives the git host from the resolved API URL, and configures git to use a
|
|
12
|
+
* cv-agent credential helper (`cva git-credential`) scoped to the cv-hub host.
|
|
13
|
+
* The helper reads the cv_pat from the existing mode-0600 credentials file on
|
|
14
|
+
* demand, so the token is never embedded in a remote URL and no new plaintext
|
|
15
|
+
* copy is created.
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.GIT_USERNAME = exports.GIT_PAT_FIELD = void 0;
|
|
19
|
+
exports.isJwt = isJwt;
|
|
20
|
+
exports.isGitCapable = isGitCapable;
|
|
21
|
+
exports.selectGitToken = selectGitToken;
|
|
22
|
+
exports.resolveGitHost = resolveGitHost;
|
|
23
|
+
exports.parseCredentialInput = parseCredentialInput;
|
|
24
|
+
exports.buildCredentialResponse = buildCredentialResponse;
|
|
25
|
+
exports.configureGitCredentialHelper = configureGitCredentialHelper;
|
|
26
|
+
exports.mintGitPat = mintGitPat;
|
|
27
|
+
const node_child_process_1 = require("node:child_process");
|
|
28
|
+
/** Credentials-file field that holds the git-capable cv_pat. */
|
|
29
|
+
exports.GIT_PAT_FIELD = 'CV_HUB_GIT_PAT';
|
|
30
|
+
/**
|
|
31
|
+
* Username presented for cv-hub git Basic auth. cv-hub ignores the username and
|
|
32
|
+
* validates the cv_pat carried in the password, but git's Basic auth requires a
|
|
33
|
+
* non-empty username, so we send a stable placeholder.
|
|
34
|
+
*/
|
|
35
|
+
exports.GIT_USERNAME = 'git';
|
|
36
|
+
const DEFAULT_GIT_HOST = 'git.hub.controlvector.io';
|
|
37
|
+
/**
|
|
38
|
+
* True if the token is a JSON Web Token: three base64url segments whose header
|
|
39
|
+
* begins with the encoding of `{"alg` (i.e. `eyJ`). cv-hub session/API tokens
|
|
40
|
+
* are JWTs; the git host rejects them.
|
|
41
|
+
*/
|
|
42
|
+
function isJwt(token) {
|
|
43
|
+
if (!token)
|
|
44
|
+
return false;
|
|
45
|
+
return /^eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]*$/.test(token.trim());
|
|
46
|
+
}
|
|
47
|
+
/** True if the token can be used as a git credential (present and not a JWT). */
|
|
48
|
+
function isGitCapable(token) {
|
|
49
|
+
return !!token && !isJwt(token);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Choose the token git should use. A dedicated git cv_pat wins; the API token
|
|
53
|
+
* is only used if it is itself git-capable (not a JWT). A JWT is never returned
|
|
54
|
+
* for git. Returns null when no git-capable token is available.
|
|
55
|
+
*/
|
|
56
|
+
function selectGitToken(gitPat, apiPat) {
|
|
57
|
+
if (isGitCapable(gitPat))
|
|
58
|
+
return gitPat.trim();
|
|
59
|
+
if (isGitCapable(apiPat))
|
|
60
|
+
return apiPat.trim();
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
/** Derive the cv-hub git host from the resolved API URL (api.* -> git.*). */
|
|
64
|
+
function resolveGitHost(apiUrl) {
|
|
65
|
+
if (!apiUrl)
|
|
66
|
+
return DEFAULT_GIT_HOST;
|
|
67
|
+
try {
|
|
68
|
+
return new URL(apiUrl).host.replace(/^api\./, 'git.');
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return DEFAULT_GIT_HOST;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/** Parse git credential helper stdin (key=value lines) into a query object. */
|
|
75
|
+
function parseCredentialInput(input) {
|
|
76
|
+
const q = {};
|
|
77
|
+
for (const line of input.split('\n')) {
|
|
78
|
+
const idx = line.indexOf('=');
|
|
79
|
+
if (idx === -1)
|
|
80
|
+
continue;
|
|
81
|
+
const key = line.slice(0, idx).trim();
|
|
82
|
+
const val = line.slice(idx + 1).trim();
|
|
83
|
+
if (key === 'protocol')
|
|
84
|
+
q.protocol = val;
|
|
85
|
+
else if (key === 'host')
|
|
86
|
+
q.host = val;
|
|
87
|
+
else if (key === 'path')
|
|
88
|
+
q.path = val;
|
|
89
|
+
else if (key === 'username')
|
|
90
|
+
q.username = val;
|
|
91
|
+
}
|
|
92
|
+
return q;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Build the git credential helper `get` response for a query, given the
|
|
96
|
+
* resolved git host and the git-capable token. Returns an empty string when the
|
|
97
|
+
* query is not for the cv-hub git host or no git-capable token is available, so
|
|
98
|
+
* git falls back (or fails) instead of presenting a wrong credential.
|
|
99
|
+
*/
|
|
100
|
+
function buildCredentialResponse(query, gitHost, token) {
|
|
101
|
+
if (!token)
|
|
102
|
+
return '';
|
|
103
|
+
if (query.host && query.host !== gitHost)
|
|
104
|
+
return '';
|
|
105
|
+
return `username=${exports.GIT_USERNAME}\npassword=${token}\n`;
|
|
106
|
+
}
|
|
107
|
+
function gitConfig(args) {
|
|
108
|
+
try {
|
|
109
|
+
(0, node_child_process_1.execFileSync)('git', ['config', ...args], { stdio: 'pipe' });
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
// Tolerate expected failures (e.g. --unset-all with nothing to remove).
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Configure git (global scope) to use the cv-agent credential helper for the
|
|
117
|
+
* cv-hub git host only. Neutralizes any inherited helper (e.g. the plain
|
|
118
|
+
* `store`) for that host so the cv-agent helper is authoritative and no
|
|
119
|
+
* plaintext copy is persisted on `git credential approve`.
|
|
120
|
+
*
|
|
121
|
+
* No token is written to any git config; only the helper invocation is stored.
|
|
122
|
+
* Idempotent.
|
|
123
|
+
*/
|
|
124
|
+
function configureGitCredentialHelper(gitHost, helperCommand = 'cva git-credential') {
|
|
125
|
+
const key = `credential.https://${gitHost}.helper`;
|
|
126
|
+
// Drop any prior cv-agent entries at this scope (idempotency).
|
|
127
|
+
gitConfig(['--global', '--unset-all', key]);
|
|
128
|
+
// Empty value resets the inherited helper list (including a global `store`)
|
|
129
|
+
// for this host; then add the cv-agent helper as the sole active helper.
|
|
130
|
+
gitConfig(['--global', '--add', key, '']);
|
|
131
|
+
gitConfig(['--global', '--add', key, `!${helperCommand}`]);
|
|
132
|
+
return {
|
|
133
|
+
gitHost,
|
|
134
|
+
scope: `https://${gitHost}`,
|
|
135
|
+
helperCommand,
|
|
136
|
+
method: 'cva-credential-helper (reads ~/.config/cv-hub/credentials, mode 0600)',
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
// ============================================================================
|
|
140
|
+
// cv_pat minting
|
|
141
|
+
// ============================================================================
|
|
142
|
+
/**
|
|
143
|
+
* Mint a git-capable cv_pat from a session/access token via the cv-hub API,
|
|
144
|
+
* mirroring the cv-git onboarding token mint. Returns null on any failure and
|
|
145
|
+
* never returns a JWT. Makes a live API call, so callers must gate it on real
|
|
146
|
+
* onboarding (not test/verification runs).
|
|
147
|
+
*/
|
|
148
|
+
async function mintGitPat(apiUrl, accessToken, username) {
|
|
149
|
+
try {
|
|
150
|
+
const res = await fetch(`${apiUrl}/api/user/tokens`, {
|
|
151
|
+
method: 'POST',
|
|
152
|
+
headers: {
|
|
153
|
+
'Content-Type': 'application/json',
|
|
154
|
+
Authorization: `Bearer ${accessToken}`,
|
|
155
|
+
},
|
|
156
|
+
body: JSON.stringify({
|
|
157
|
+
name: `cv-agent-${username || 'auto'}`,
|
|
158
|
+
scopes: ['repo:read', 'repo:write'],
|
|
159
|
+
expiresInDays: 365,
|
|
160
|
+
}),
|
|
161
|
+
});
|
|
162
|
+
if (!res.ok)
|
|
163
|
+
return null;
|
|
164
|
+
const data = (await res.json());
|
|
165
|
+
return isGitCapable(data.token) ? data.token.trim() : null;
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
//# sourceMappingURL=git-credentials.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git-credentials.js","sourceRoot":"","sources":["../../src/utils/git-credentials.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cv-meter token-metering plumbing.
|
|
3
|
+
*
|
|
4
|
+
* Pure helpers (no I/O) so the env wiring is unit-testable. The agent reads
|
|
5
|
+
* meter config once at startup and injects it into every Claude Code spawn;
|
|
6
|
+
* cv-code (the producer) reads CVMETER_* from that env and emits usage events.
|
|
7
|
+
*/
|
|
8
|
+
import type { CvaConfig, MeterEnforce } from './config.js';
|
|
9
|
+
export interface MeterResolution {
|
|
10
|
+
url?: string;
|
|
11
|
+
token?: string;
|
|
12
|
+
enforce: MeterEnforce;
|
|
13
|
+
org?: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Resolve meter config from agent config + process env.
|
|
17
|
+
* Config file wins over env (explicit config beats ambient environment).
|
|
18
|
+
*/
|
|
19
|
+
export declare function resolveMeterConfig(config: Partial<CvaConfig>, env: NodeJS.ProcessEnv): MeterResolution;
|
|
20
|
+
export type MeterReasonCode = 'OK' | 'SOFT_WARN' | 'HARD_DENY' | 'NO_BUDGET';
|
|
21
|
+
export type MeterAction = 'allow' | 'warn' | 'deny';
|
|
22
|
+
export interface MeterCheckScopes {
|
|
23
|
+
user?: string;
|
|
24
|
+
project?: string;
|
|
25
|
+
task?: string;
|
|
26
|
+
org?: string;
|
|
27
|
+
}
|
|
28
|
+
export interface MeterCheckResult {
|
|
29
|
+
action: MeterAction;
|
|
30
|
+
reasonCode: MeterReasonCode;
|
|
31
|
+
reason: string;
|
|
32
|
+
governingKey?: string;
|
|
33
|
+
}
|
|
34
|
+
export declare function mapReasonToAction(code: MeterReasonCode): MeterAction;
|
|
35
|
+
export declare function resolveErrorAction(enforce: MeterEnforce): MeterAction;
|
|
36
|
+
export declare function pollBackoffMs(baseIntervalMs: number, denyStreak: number, capMs?: number): number;
|
|
37
|
+
export declare function noteDeny(cache: Map<string, number>, governingKey: string | undefined, now: number, ttlMs?: number): void;
|
|
38
|
+
export declare function checkDenyCache(cache: Map<string, number>, scopes: MeterCheckScopes, now: number): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Call cv-meter POST /v1/check. Returns the mapped server verdict on HTTP 200, or
|
|
41
|
+
* null on any error/timeout/non-200 (caller applies resolveErrorAction). Never throws.
|
|
42
|
+
* ~1s target timeout, 3s hard ceiling. Side-effect-free ⇒ safe to skip/retry.
|
|
43
|
+
*/
|
|
44
|
+
export declare function meterCheck(meter: MeterResolution, org: string, scopes: MeterCheckScopes, timeoutMs?: number): Promise<MeterCheckResult | null>;
|
|
45
|
+
/**
|
|
46
|
+
* Inject resolved meter config into a spawn env (mutates in place).
|
|
47
|
+
* The token is only set when a URL is present — a token without an endpoint is
|
|
48
|
+
* useless and we don't want to leak it into a child that won't meter.
|
|
49
|
+
*/
|
|
50
|
+
export declare function applyMeterEnv(targetEnv: NodeJS.ProcessEnv, meter: MeterResolution): void;
|
|
51
|
+
/**
|
|
52
|
+
* Clone a base spawn env and stamp the per-task CVMETER_TASK_ID.
|
|
53
|
+
*
|
|
54
|
+
* Returns a fresh object so the daemon's base env is never mutated — this is
|
|
55
|
+
* what prevents one task's id from leaking into the next task's events. The id
|
|
56
|
+
* is only stamped when metering is active (CVMETER_URL present in the base env).
|
|
57
|
+
*/
|
|
58
|
+
export declare function stampTaskEnv(baseEnv: NodeJS.ProcessEnv, taskId: string): NodeJS.ProcessEnv;
|
|
59
|
+
//# sourceMappingURL=meter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meter.d.ts","sourceRoot":"","sources":["../../src/utils/meter.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3D,MAAM,WAAW,eAAe;IAC9B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,YAAY,CAAC;IACtB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,OAAO,CAAC,SAAS,CAAC,EAC1B,GAAG,EAAE,MAAM,CAAC,UAAU,GACrB,eAAe,CAOjB;AAOD,MAAM,MAAM,eAAe,GAAG,IAAI,GAAG,WAAW,GAAG,WAAW,GAAG,WAAW,CAAC;AAC7E,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;AACpD,MAAM,WAAW,gBAAgB;IAAG,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE;AAClG,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,WAAW,CAAC;IACpB,UAAU,EAAE,eAAe,CAAC;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAGD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,eAAe,GAAG,WAAW,CAEpE;AAGD,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,YAAY,GAAG,WAAW,CAErE;AAID,wBAAgB,aAAa,CAAC,cAAc,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,SAAa,GAAG,MAAM,CAGpG;AAID,wBAAgB,QAAQ,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,YAAY,EAAE,MAAM,GAAG,SAAS,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,SAAS,GAAG,IAAI,CAExH;AACD,wBAAgB,cAAc,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,gBAAgB,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAQzG;AAED;;;;GAIG;AACH,wBAAsB,UAAU,CAC9B,KAAK,EAAE,eAAe,EACtB,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,gBAAgB,EACxB,SAAS,SAAO,GACf,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAyBlC;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,CAAC,UAAU,EAAE,KAAK,EAAE,eAAe,GAAG,IAAI,CAIxF;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,UAAU,CAM1F"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* cv-meter token-metering plumbing.
|
|
4
|
+
*
|
|
5
|
+
* Pure helpers (no I/O) so the env wiring is unit-testable. The agent reads
|
|
6
|
+
* meter config once at startup and injects it into every Claude Code spawn;
|
|
7
|
+
* cv-code (the producer) reads CVMETER_* from that env and emits usage events.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.resolveMeterConfig = resolveMeterConfig;
|
|
11
|
+
exports.mapReasonToAction = mapReasonToAction;
|
|
12
|
+
exports.resolveErrorAction = resolveErrorAction;
|
|
13
|
+
exports.pollBackoffMs = pollBackoffMs;
|
|
14
|
+
exports.noteDeny = noteDeny;
|
|
15
|
+
exports.checkDenyCache = checkDenyCache;
|
|
16
|
+
exports.meterCheck = meterCheck;
|
|
17
|
+
exports.applyMeterEnv = applyMeterEnv;
|
|
18
|
+
exports.stampTaskEnv = stampTaskEnv;
|
|
19
|
+
/**
|
|
20
|
+
* Resolve meter config from agent config + process env.
|
|
21
|
+
* Config file wins over env (explicit config beats ambient environment).
|
|
22
|
+
*/
|
|
23
|
+
function resolveMeterConfig(config, env) {
|
|
24
|
+
return {
|
|
25
|
+
url: config.meter_url ?? env.CVMETER_URL,
|
|
26
|
+
token: config.meter_token ?? env.CVMETER_TOKEN,
|
|
27
|
+
enforce: (config.meter_enforce ?? env.CVMETER_ENFORCE ?? 'off'),
|
|
28
|
+
org: config.meter_org ?? env.CVMETER_ORG,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
// Pure: map the server's reason_code to a gate action. NO_BUDGET/OK ⇒ allow.
|
|
32
|
+
function mapReasonToAction(code) {
|
|
33
|
+
return code === 'HARD_DENY' ? 'deny' : code === 'SOFT_WARN' ? 'warn' : 'allow';
|
|
34
|
+
}
|
|
35
|
+
// Pure: on a check error/timeout, fail-open (allow) unless configured fail_closed (deny).
|
|
36
|
+
function resolveErrorAction(enforce) {
|
|
37
|
+
return enforce === 'fail_closed' ? 'deny' : 'allow';
|
|
38
|
+
}
|
|
39
|
+
// Pure: poll sleep with exponential backoff after consecutive budget denials (cap 5 min).
|
|
40
|
+
// denyStreak 0 ⇒ base interval; resets are the caller's job (any allow/warn sets streak 0).
|
|
41
|
+
function pollBackoffMs(baseIntervalMs, denyStreak, capMs = 5 * 60_000) {
|
|
42
|
+
if (denyStreak <= 0)
|
|
43
|
+
return baseIntervalMs;
|
|
44
|
+
return Math.min(baseIntervalMs * 2 ** Math.min(denyStreak - 1, 6), capMs);
|
|
45
|
+
}
|
|
46
|
+
// Pure: negative-cache of denied scope keys → expiry, so a burst of same-scope tasks
|
|
47
|
+
// doesn't fire N /v1/check calls for a scope already known over-cap.
|
|
48
|
+
function noteDeny(cache, governingKey, now, ttlMs = 90_000) {
|
|
49
|
+
if (governingKey)
|
|
50
|
+
cache.set(governingKey, now + ttlMs);
|
|
51
|
+
}
|
|
52
|
+
function checkDenyCache(cache, scopes, now) {
|
|
53
|
+
for (const [k, v] of Object.entries(scopes)) {
|
|
54
|
+
if (!v)
|
|
55
|
+
continue;
|
|
56
|
+
const until = cache.get(`${k}:${v}`);
|
|
57
|
+
if (until && until > now)
|
|
58
|
+
return true;
|
|
59
|
+
if (until && until <= now)
|
|
60
|
+
cache.delete(`${k}:${v}`); // lazy eviction
|
|
61
|
+
}
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Call cv-meter POST /v1/check. Returns the mapped server verdict on HTTP 200, or
|
|
66
|
+
* null on any error/timeout/non-200 (caller applies resolveErrorAction). Never throws.
|
|
67
|
+
* ~1s target timeout, 3s hard ceiling. Side-effect-free ⇒ safe to skip/retry.
|
|
68
|
+
*/
|
|
69
|
+
async function meterCheck(meter, org, scopes, timeoutMs = 1000) {
|
|
70
|
+
if (!meter.url)
|
|
71
|
+
return null;
|
|
72
|
+
const ctl = new AbortController();
|
|
73
|
+
const timer = setTimeout(() => ctl.abort(), Math.min(timeoutMs, 3000));
|
|
74
|
+
try {
|
|
75
|
+
const res = await fetch(`${meter.url.replace(/\/$/, '')}/v1/check`, {
|
|
76
|
+
method: 'POST',
|
|
77
|
+
headers: { 'Content-Type': 'application/json', ...(meter.token ? { Authorization: `Bearer ${meter.token}` } : {}) },
|
|
78
|
+
body: JSON.stringify({ org, scopes }),
|
|
79
|
+
signal: ctl.signal,
|
|
80
|
+
});
|
|
81
|
+
if (!res.ok)
|
|
82
|
+
return null; // 400/401/403/5xx are errors, not budget denials
|
|
83
|
+
const body = await res.json();
|
|
84
|
+
const g = body.governing;
|
|
85
|
+
return {
|
|
86
|
+
action: mapReasonToAction(body.reason_code),
|
|
87
|
+
reasonCode: body.reason_code,
|
|
88
|
+
reason: body.reason ?? '',
|
|
89
|
+
governingKey: g ? `${g.scope}:${g.scope_id}` : undefined,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return null; // timeout / network / abort
|
|
94
|
+
}
|
|
95
|
+
finally {
|
|
96
|
+
clearTimeout(timer);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Inject resolved meter config into a spawn env (mutates in place).
|
|
101
|
+
* The token is only set when a URL is present — a token without an endpoint is
|
|
102
|
+
* useless and we don't want to leak it into a child that won't meter.
|
|
103
|
+
*/
|
|
104
|
+
function applyMeterEnv(targetEnv, meter) {
|
|
105
|
+
if (!meter.url)
|
|
106
|
+
return;
|
|
107
|
+
targetEnv.CVMETER_URL = meter.url;
|
|
108
|
+
if (meter.token)
|
|
109
|
+
targetEnv.CVMETER_TOKEN = meter.token;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Clone a base spawn env and stamp the per-task CVMETER_TASK_ID.
|
|
113
|
+
*
|
|
114
|
+
* Returns a fresh object so the daemon's base env is never mutated — this is
|
|
115
|
+
* what prevents one task's id from leaking into the next task's events. The id
|
|
116
|
+
* is only stamped when metering is active (CVMETER_URL present in the base env).
|
|
117
|
+
*/
|
|
118
|
+
function stampTaskEnv(baseEnv, taskId) {
|
|
119
|
+
const taskEnv = { ...baseEnv };
|
|
120
|
+
if (taskEnv.CVMETER_URL) {
|
|
121
|
+
taskEnv.CVMETER_TASK_ID = taskId;
|
|
122
|
+
}
|
|
123
|
+
return taskEnv;
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=meter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meter.js","sourceRoot":"","sources":["../../src/utils/meter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent profiles (P14). A profile is data, not a fork: a manifest + system prompt +
|
|
3
|
+
* tool policy that constrains what a cva session can do. It is loaded at start and
|
|
4
|
+
* enforced as a hard gate, so a profiled session cannot exceed its manifest no matter
|
|
5
|
+
* what the model attempts. cv-deploy (P15) is the first profile.
|
|
6
|
+
*
|
|
7
|
+
* This module is pure and self-contained: the manifest schema, the loader, and the
|
|
8
|
+
* enforcement decisions (tool policy, task-type routing, scope ceiling, base semver
|
|
9
|
+
* gate) are all here as testable functions. agent.ts wires them into the run loop and
|
|
10
|
+
* the permission relay; the frozen contract other profiles author against is SPEC.md.
|
|
11
|
+
*/
|
|
12
|
+
import { z } from 'zod';
|
|
13
|
+
/**
|
|
14
|
+
* The profile manifest. `.strict()` at every level makes an unknown key an ERROR, not
|
|
15
|
+
* a warning (P14 item 2): a typo or a key from a newer schema fails the load loudly
|
|
16
|
+
* rather than being silently ignored.
|
|
17
|
+
*/
|
|
18
|
+
export declare const ProfileManifestSchema: z.ZodObject<{
|
|
19
|
+
name: z.ZodString;
|
|
20
|
+
version: z.ZodString;
|
|
21
|
+
base: z.ZodString;
|
|
22
|
+
system_prompt: z.ZodString;
|
|
23
|
+
tools: z.ZodObject<{
|
|
24
|
+
allow: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
25
|
+
deny: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
26
|
+
}, z.core.$strict>;
|
|
27
|
+
task_types: z.ZodArray<z.ZodString>;
|
|
28
|
+
credential_scopes: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
29
|
+
limits: z.ZodDefault<z.ZodObject<{
|
|
30
|
+
max_turns: z.ZodOptional<z.ZodNumber>;
|
|
31
|
+
max_tool_calls: z.ZodOptional<z.ZodNumber>;
|
|
32
|
+
timeout_s: z.ZodOptional<z.ZodNumber>;
|
|
33
|
+
}, z.core.$strict>>;
|
|
34
|
+
}, z.core.$strict>;
|
|
35
|
+
export type ProfileManifest = z.infer<typeof ProfileManifestSchema>;
|
|
36
|
+
export declare class ProfileError extends Error {
|
|
37
|
+
readonly detail: Record<string, unknown>;
|
|
38
|
+
constructor(message: string, detail?: Record<string, unknown>);
|
|
39
|
+
}
|
|
40
|
+
/** Validate an already-parsed object as a manifest. Throws ProfileError on any issue. */
|
|
41
|
+
export declare function validateManifest(input: unknown): ProfileManifest;
|
|
42
|
+
/**
|
|
43
|
+
* Check the manifest `base` constraint against the running host. The grammar is
|
|
44
|
+
* frozen (SPEC): `[<host-name>] <op> <version>`, op one of >= > <= < =, version a
|
|
45
|
+
* bare `major.minor.patch`; a bare version with no op means `=`. A host-name token,
|
|
46
|
+
* when present, must match hostName (a profile for a different host is refused).
|
|
47
|
+
*/
|
|
48
|
+
export declare function checkBaseConstraint(base: string, hostName: string, hostVersion: string): {
|
|
49
|
+
ok: boolean;
|
|
50
|
+
reason?: string;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* A tool spec's base name. Claude Code tools appear as "Bash" or scoped like
|
|
54
|
+
* "Bash(git*)"; policy entries may be "Bash", "Bash(*)", or "Bash(git*)". Policy is
|
|
55
|
+
* evaluated by base name so an allow/deny of "Bash" covers every Bash invocation.
|
|
56
|
+
*/
|
|
57
|
+
export declare function toolBaseName(spec: string): string;
|
|
58
|
+
export interface ToolDecision {
|
|
59
|
+
allowed: boolean;
|
|
60
|
+
reason?: string;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The tool-policy decision: a deny-list entry wins over an allow-list entry, and
|
|
64
|
+
* absence from the allow-list means denied. This is the hard gate the relay and the
|
|
65
|
+
* auto-approve allowlist are both built from.
|
|
66
|
+
*/
|
|
67
|
+
export declare function evaluateTool(manifest: ProfileManifest, tool: string): ToolDecision;
|
|
68
|
+
/** The `--allowedTools` list for auto-approve mode: allow entries not shadowed by a deny. */
|
|
69
|
+
export declare function allowedToolsList(manifest: ProfileManifest): string[];
|
|
70
|
+
/** Pull the tool name out of a Claude Code permission prompt, or null if not present. */
|
|
71
|
+
export declare function extractToolFromPrompt(promptText: string): string | null;
|
|
72
|
+
/**
|
|
73
|
+
* The relay decision for a permission prompt under a profile. A tool the profile
|
|
74
|
+
* denies is refused AT THE RELAY (action "deny" -> the relay answers no); an allowed
|
|
75
|
+
* (or unidentifiable) tool falls through to the normal human/CV-Hub relay. The profile
|
|
76
|
+
* only hard-denies; it never auto-approves what the operator would otherwise gate.
|
|
77
|
+
*/
|
|
78
|
+
export declare function relayPermissionDecision(manifest: ProfileManifest, promptText: string): {
|
|
79
|
+
action: 'deny' | 'relay';
|
|
80
|
+
tool?: string;
|
|
81
|
+
reason?: string;
|
|
82
|
+
};
|
|
83
|
+
/** A dispatched task whose type is outside `task_types` is refused (before any model call). */
|
|
84
|
+
export declare function checkTaskType(manifest: ProfileManifest, taskType: string): ToolDecision;
|
|
85
|
+
/**
|
|
86
|
+
* Apply the credential-scope ceiling: keep only offered scopes that the profile
|
|
87
|
+
* permits, dropping anything broader. The profile's credential_scopes is a MAXIMUM,
|
|
88
|
+
* so this is the intersection (offered that is within the ceiling).
|
|
89
|
+
*/
|
|
90
|
+
export declare function capScopes(ceiling: string[], offered: string[]): string[];
|
|
91
|
+
export interface LoadedProfile {
|
|
92
|
+
manifest: ProfileManifest;
|
|
93
|
+
systemPrompt: string;
|
|
94
|
+
/** Directory the manifest was loaded from (system_prompt resolves against it). */
|
|
95
|
+
dir: string;
|
|
96
|
+
/** The spec the caller passed (a path or an @scope/pkg). */
|
|
97
|
+
source: string;
|
|
98
|
+
}
|
|
99
|
+
export interface LoadProfileOptions {
|
|
100
|
+
/** Running host version, e.g. the cva version, for the base gate. */
|
|
101
|
+
hostVersion: string;
|
|
102
|
+
/** Host binary name the base constraint may name. Defaults to "cv-agent". */
|
|
103
|
+
hostName?: string;
|
|
104
|
+
/** Injectable file reader (tests). Defaults to fs.readFileSync utf8. */
|
|
105
|
+
readFile?: (path: string) => string;
|
|
106
|
+
/** Injectable package resolver for @scope/pkg specs (tests). Defaults to require.resolve. */
|
|
107
|
+
resolvePackage?: (spec: string) => string;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Resolve a profile spec to a manifest file path. A spec beginning with "@" (or any
|
|
111
|
+
* bare package name) is resolved as a package whose root holds profile.yaml; anything
|
|
112
|
+
* else is a filesystem path (a directory implies <dir>/profile.yaml, a file is used
|
|
113
|
+
* as-is).
|
|
114
|
+
*/
|
|
115
|
+
export declare function resolveProfileSpec(spec: string, opts: LoadProfileOptions): string;
|
|
116
|
+
/**
|
|
117
|
+
* Load and validate a profile: resolve the spec, parse + strictly validate the
|
|
118
|
+
* manifest, gate the host version against `base`, then load the system prompt. Every
|
|
119
|
+
* failure is a ProfileError with a specific reason (P14: unknown keys, bad semver, and
|
|
120
|
+
* a failed base gate are all hard errors).
|
|
121
|
+
*/
|
|
122
|
+
export declare function loadProfile(spec: string, opts: LoadProfileOptions): LoadedProfile;
|
|
123
|
+
//# sourceMappingURL=profile.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"profile.d.ts","sourceRoot":"","sources":["../../src/utils/profile.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAMH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAuBxB;;;;GAIG;AACH,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;kBAcvB,CAAC;AAEZ,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE,qBAAa,YAAa,SAAQ,KAAK;IAGnC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;gBADxC,OAAO,EAAE,MAAM,EACN,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM;CAKhD;AAED,yFAAyF;AACzF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,eAAe,CAOhE;AAsBD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,GAClB;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAuBlC;AAMD;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGjD;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,GAAG,YAAY,CAOlF;AAED,6FAA6F;AAC7F,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,eAAe,GAAG,MAAM,EAAE,CAGpE;AAED,yFAAyF;AACzF,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAGvE;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,eAAe,EACzB,UAAU,EAAE,MAAM,GACjB;IAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAM9D;AAMD,+FAA+F;AAC/F,wBAAgB,aAAa,CAAC,QAAQ,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,GAAG,YAAY,CAMvF;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAGxE;AAMD,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,eAAe,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,kFAAkF;IAClF,GAAG,EAAE,MAAM,CAAC;IACZ,4DAA4D;IAC5D,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,kBAAkB;IACjC,qEAAqE;IACrE,WAAW,EAAE,MAAM,CAAC;IACpB,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wEAAwE;IACxE,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IACpC,6FAA6F;IAC7F,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;CAC3C;AAMD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,kBAAkB,GAAG,MAAM,CAOjF;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,kBAAkB,GAAG,aAAa,CAyCjF"}
|