@refleet-it/runner 0.1.199 → 0.1.200
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/backends/claude.js +2 -1
- package/dist/backends/kiro.js +2 -2
- package/dist/backends/types.js +9 -0
- package/dist/fleet/api.js +4 -2
- package/dist/fleet/git.js +14 -7
- package/dist/fleet/gitlab.js +1 -1
- package/dist/fleet/job.js +9 -2
- package/dist/fleet/workspace.js +5 -5
- package/package.json +1 -1
package/dist/backends/claude.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
-
import { AgentExecutionError } from './types.js';
|
|
2
|
+
import { agentEnv, AgentExecutionError } from './types.js';
|
|
3
3
|
/**
|
|
4
4
|
* Builds the `claude` CLI argument list for one job kind. Ported 1:1 from
|
|
5
5
|
* runner/claude/entrypoint.sh's build_claude_args/build_kind_claude_args: qualification
|
|
@@ -107,6 +107,7 @@ export class ClaudeBackend {
|
|
|
107
107
|
let buffer = '';
|
|
108
108
|
const child = this.spawnFn(this.executablePath, args, {
|
|
109
109
|
cwd: opts.cwd,
|
|
110
|
+
env: agentEnv(this.env),
|
|
110
111
|
stdio: ['ignore', 'pipe', 'inherit'],
|
|
111
112
|
});
|
|
112
113
|
child.stdout.setEncoding('utf8');
|
package/dist/backends/kiro.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
-
import { AgentExecutionError } from './types.js';
|
|
2
|
+
import { agentEnv, AgentExecutionError } from './types.js';
|
|
3
3
|
/**
|
|
4
4
|
* Pulls the ACP session id out of a `session/new` (or `session/load`) response.
|
|
5
5
|
* Pure/testable in isolation from the actual JSON-RPC transport.
|
|
@@ -177,7 +177,7 @@ export class KiroBackend {
|
|
|
177
177
|
// protocol-native session/request_permission (see AcpConnection.handlePeerRequest).
|
|
178
178
|
const child = this.spawnFn(this.executablePath, ['acp'], {
|
|
179
179
|
cwd: opts.cwd,
|
|
180
|
-
env: this.env,
|
|
180
|
+
env: agentEnv(this.env),
|
|
181
181
|
stdio: ['pipe', 'pipe', 'inherit'],
|
|
182
182
|
});
|
|
183
183
|
const chunks = [];
|
package/dist/backends/types.js
CHANGED
|
@@ -1,2 +1,11 @@
|
|
|
1
1
|
export class AgentExecutionError extends Error {
|
|
2
2
|
}
|
|
3
|
+
/**
|
|
4
|
+
* The agent runs untrusted code and prompts from a customer repository with tool
|
|
5
|
+
* permissions switched off, so anything it can read from its environment it can also be
|
|
6
|
+
* talked into exfiltrating. The runner's own credentials — the API key that fetches
|
|
7
|
+
* GitLab tokens — have no business there.
|
|
8
|
+
*/
|
|
9
|
+
export function agentEnv(env) {
|
|
10
|
+
return Object.fromEntries(Object.entries(env).filter(([name]) => !name.startsWith('REFLEET_')));
|
|
11
|
+
}
|
package/dist/fleet/api.js
CHANGED
|
@@ -92,8 +92,10 @@ export class FleetApi {
|
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
94
|
/**
|
|
95
|
-
* Fetched
|
|
96
|
-
*
|
|
95
|
+
* Fetched right before every git/GitLab call rather than once per job or at startup:
|
|
96
|
+
* OAuth-backed connections hand out short-lived access tokens, and the backend refreshes
|
|
97
|
+
* one only when asked for it, so a token fetched before a long agent run may be dead by
|
|
98
|
+
* the time the branch is pushed.
|
|
97
99
|
*/
|
|
98
100
|
async fetchGitLabCredentials() {
|
|
99
101
|
const response = await this.request('GET', '/runner/gitlab-credentials');
|
package/dist/fleet/git.js
CHANGED
|
@@ -11,18 +11,25 @@ export class GitError extends Error {
|
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
13
|
/**
|
|
14
|
-
* GitLab's git-http backend (unlike its REST API) does not accept
|
|
14
|
+
* GitLab's git-http backend (unlike its REST API) does not accept a bearer/private token
|
|
15
15
|
* header — it only honors HTTP Basic auth, so the token is sent as a Basic credential
|
|
16
|
-
* (
|
|
17
|
-
*
|
|
16
|
+
* (the `oauth2` username works for both OAuth and personal/group access tokens).
|
|
17
|
+
* Handed to git through GIT_CONFIG_* environment variables rather than `-c` on the
|
|
18
|
+
* command line, so it is never written into the cached checkout's git config, never
|
|
19
|
+
* visible in `ps`, and never part of the argv a GitError echoes back into logs and job
|
|
20
|
+
* failure reports.
|
|
18
21
|
*/
|
|
19
|
-
export function
|
|
22
|
+
export function gitAuthEnv(accessToken) {
|
|
20
23
|
const basic = Buffer.from(`oauth2:${accessToken}`).toString('base64');
|
|
21
|
-
return
|
|
24
|
+
return {
|
|
25
|
+
GIT_CONFIG_COUNT: '1',
|
|
26
|
+
GIT_CONFIG_KEY_0: 'http.extraHeader',
|
|
27
|
+
GIT_CONFIG_VALUE_0: `Authorization: Basic ${basic}`,
|
|
28
|
+
};
|
|
22
29
|
}
|
|
23
30
|
export function createGitRunner(execFileFn = execFile) {
|
|
24
|
-
return (args, cwd) => new Promise((resolve, reject) => {
|
|
25
|
-
execFileFn('git', args, { cwd, maxBuffer: 64 * 1024 * 1024, env: { ...process.env, GIT_TERMINAL_PROMPT: '0' } }, (error, stdout, stderr) => {
|
|
31
|
+
return (args, cwd, env = {}) => new Promise((resolve, reject) => {
|
|
32
|
+
execFileFn('git', args, { cwd, maxBuffer: 64 * 1024 * 1024, env: { ...process.env, GIT_TERMINAL_PROMPT: '0', ...env } }, (error, stdout, stderr) => {
|
|
26
33
|
if (error) {
|
|
27
34
|
const code = 'code' in error && 'number' === typeof error.code ? error.code : null;
|
|
28
35
|
reject(new GitError(args, code, String(stderr)));
|
package/dist/fleet/gitlab.js
CHANGED
|
@@ -19,7 +19,7 @@ async function gitlabRequest(credentials, fetchFn, method, path, body) {
|
|
|
19
19
|
return fetchFn(`${apiBase(credentials)}${path}`, {
|
|
20
20
|
method,
|
|
21
21
|
headers: {
|
|
22
|
-
|
|
22
|
+
Authorization: `Bearer ${credentials.accessToken}`,
|
|
23
23
|
'Content-Type': 'application/json',
|
|
24
24
|
},
|
|
25
25
|
body: undefined === body ? undefined : JSON.stringify(body),
|
package/dist/fleet/job.js
CHANGED
|
@@ -83,9 +83,16 @@ export async function claimAndRunJob(deps) {
|
|
|
83
83
|
}
|
|
84
84
|
return;
|
|
85
85
|
}
|
|
86
|
-
await publishAndReport(deps, job,
|
|
86
|
+
await publishAndReport(deps, job, repoDir, project, output, report);
|
|
87
87
|
}
|
|
88
|
-
async function publishAndReport(deps, job,
|
|
88
|
+
async function publishAndReport(deps, job, repoDir, project, output, report) {
|
|
89
|
+
// The agent may have run for longer than the access token fetched for the clone lives.
|
|
90
|
+
const credentials = await deps.api.fetchGitLabCredentials();
|
|
91
|
+
if (!credentials) {
|
|
92
|
+
deps.log(`job ${job.jobId}: failed to fetch GitLab credentials from Slipway before publishing`);
|
|
93
|
+
await report('failure', 'Failed to fetch GitLab credentials from Slipway', { errorMessage: 'gitlab credentials fetch failed' });
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
89
96
|
let published;
|
|
90
97
|
try {
|
|
91
98
|
published = await (deps.publishChange ?? defaultPublishChange)(deps.workspace, credentials, repoDir, project, job.ownerTargetId ?? '', `Apply Slipway shift ${job.ownerId ?? ''}`, tail(output), deps.fetchFn);
|
package/dist/fleet/workspace.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { appendFileSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, utimesSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
-
import {
|
|
3
|
+
import { gitAuthEnv } from './git.js';
|
|
4
4
|
import { createMergeRequest, ensureRefleetLabel, REFLEET_LABEL } from './gitlab.js';
|
|
5
5
|
const LAST_USED_MARKER = '.slipway-last-used';
|
|
6
6
|
function directorySizeBytes(dir) {
|
|
@@ -82,10 +82,10 @@ function excludeMarkerFromGit(repoDir) {
|
|
|
82
82
|
/** Returns the ready-to-use working directory; throws (GitError) when any git step fails. */
|
|
83
83
|
export async function syncRepo(workspace, credentials, project) {
|
|
84
84
|
const repoDir = join(workspace.cacheDir, project.externalId);
|
|
85
|
-
const auth =
|
|
85
|
+
const auth = gitAuthEnv(credentials.accessToken);
|
|
86
86
|
if (existsSync(join(repoDir, '.git'))) {
|
|
87
87
|
workspace.log(`reusing cached checkout for ${project.path}`);
|
|
88
|
-
await workspace.git([
|
|
88
|
+
await workspace.git(['fetch', '--prune', 'origin'], repoDir, auth);
|
|
89
89
|
await workspace.git(['checkout', '-f', project.defaultBranch], repoDir);
|
|
90
90
|
await workspace.git(['reset', '--hard', `origin/${project.defaultBranch}`], repoDir);
|
|
91
91
|
await workspace.git(['clean', '-fdx'], repoDir);
|
|
@@ -95,7 +95,7 @@ export async function syncRepo(workspace, credentials, project) {
|
|
|
95
95
|
evictLruUntilUnderCap(workspace);
|
|
96
96
|
workspace.log(`cloning ${project.path} (first use on this runner)`);
|
|
97
97
|
const cloneUrl = `${credentials.baseUrl.replace(/\/+$/, '')}/${project.path}.git`;
|
|
98
|
-
await workspace.git([
|
|
98
|
+
await workspace.git(['clone', '--origin', 'origin', cloneUrl, repoDir], undefined, auth);
|
|
99
99
|
}
|
|
100
100
|
touchMarker(repoDir);
|
|
101
101
|
excludeMarkerFromGit(repoDir);
|
|
@@ -118,7 +118,7 @@ export async function publishChange(workspace, credentials, repoDir, project, sh
|
|
|
118
118
|
return null;
|
|
119
119
|
}
|
|
120
120
|
await workspace.git(['-c', 'user.name=Slipway', '-c', 'user.email=slipway@localhost', 'commit', '-m', title], repoDir);
|
|
121
|
-
await workspace.git([
|
|
121
|
+
await workspace.git(['push', '--force', 'origin', `HEAD:refs/heads/${branchName}`], repoDir, gitAuthEnv(credentials.accessToken));
|
|
122
122
|
// Without the label in place GitLab would create it in a random colour, so the MR
|
|
123
123
|
// rather goes out unlabelled than off-brand.
|
|
124
124
|
const labelled = await ensureRefleetLabel(credentials, project.externalId, fetchFn);
|
package/package.json
CHANGED