@cat-factory/executor-harness 1.39.3 → 1.41.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/server.js +9 -1
- package/dist/version.js +44 -0
- package/package.json +3 -3
- package/src/server.ts +9 -1
- package/src/version.ts +45 -0
package/dist/server.js
CHANGED
|
@@ -6,6 +6,7 @@ import { handleInline } from './inline.js';
|
|
|
6
6
|
import { redactSecrets } from './git.js';
|
|
7
7
|
import { JobRegistry, loadRunnerLimits } from './runner.js';
|
|
8
8
|
import { log } from './logger.js';
|
|
9
|
+
import { HARNESS_VERSION } from './version.js';
|
|
9
10
|
// The container's HTTP entry point. The Worker addresses one instance per run and
|
|
10
11
|
// POSTs a job to /jobs (the body's `kind` selects which agent runs); the harness
|
|
11
12
|
// starts that job in the background (bounded by an inactivity + max-duration
|
|
@@ -90,7 +91,14 @@ function send(res, status, body) {
|
|
|
90
91
|
const server = createServer((req, res) => {
|
|
91
92
|
void (async () => {
|
|
92
93
|
if (req.method === 'GET' && req.url === '/health') {
|
|
93
|
-
|
|
94
|
+
// Report the harness version so a backend can detect a stale/mismatched executor and
|
|
95
|
+
// fail loudly early (see version.ts). Unauthenticated like the rest of /health — the
|
|
96
|
+
// version is not a secret. An old image predating this field simply omits it, which the
|
|
97
|
+
// backend treats as a stale signal.
|
|
98
|
+
return send(res, 200, {
|
|
99
|
+
status: 'ok',
|
|
100
|
+
...(HARNESS_VERSION ? { version: HARNESS_VERSION } : {}),
|
|
101
|
+
});
|
|
94
102
|
}
|
|
95
103
|
// All non-health endpoints are gated by the optional shared secret.
|
|
96
104
|
if (!authorized(req)) {
|
package/dist/version.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
// The harness's OWN version, resolved once at module load and reported on `/health`. A
|
|
5
|
+
// backend reads it back over the health handshake to detect a STALE or MISMATCHED executor
|
|
6
|
+
// (an old image left behind by a mutable tag, an outdated native install) and fail loudly
|
|
7
|
+
// and early — instead of the cryptic downstream symptom a version skew otherwise produces
|
|
8
|
+
// (e.g. a since-removed git flag reappearing and breaking every authenticated clone/push).
|
|
9
|
+
//
|
|
10
|
+
// Resolution order, most authoritative first:
|
|
11
|
+
// 1. `HARNESS_VERSION` env — the Docker image can bake it; an operator can override.
|
|
12
|
+
// 2. the version file the image writes next to `dist/` (the image deliberately ships NO
|
|
13
|
+
// package.json, so it captures the version into `harness-version.txt` at build time).
|
|
14
|
+
// 3. `package.json` — the native/npm install and a source checkout both keep it beside
|
|
15
|
+
// `dist/`, so `dist/version.js` finds it one level up.
|
|
16
|
+
// Undefined only for an oddly-assembled runtime carrying none of the three; the backend then
|
|
17
|
+
// treats "no reported version" as a strong stale signal in its own right.
|
|
18
|
+
function readVersionFile(dir, rel) {
|
|
19
|
+
try {
|
|
20
|
+
const raw = readFileSync(join(dir, rel), 'utf8');
|
|
21
|
+
const value = rel.endsWith('.json') ? JSON.parse(raw).version : raw;
|
|
22
|
+
const trimmed = value?.trim();
|
|
23
|
+
return trimmed || undefined;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function resolveHarnessVersion() {
|
|
30
|
+
const fromEnv = process.env.HARNESS_VERSION?.trim();
|
|
31
|
+
if (fromEnv)
|
|
32
|
+
return fromEnv;
|
|
33
|
+
let dir;
|
|
34
|
+
try {
|
|
35
|
+
// Compiled to `dist/version.js`; the baked file / package.json sit one level up from dist.
|
|
36
|
+
dir = dirname(fileURLToPath(import.meta.url));
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
return readVersionFile(dir, '../harness-version.txt') ?? readVersionFile(dir, '../package.json');
|
|
42
|
+
}
|
|
43
|
+
/** The running harness version, or undefined when it cannot be determined. */
|
|
44
|
+
export const HARNESS_VERSION = resolveHarnessVersion();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cat-factory/executor-harness",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.41.0",
|
|
4
4
|
"description": "Container payload: a thin TypeScript wrapper that runs the Pi coding agent against a cloned repo and opens a PR. Runs in the Cloudflare Container (and, in local native mode, as a host process); carries no secrets.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
"hono": "^4.12.27",
|
|
27
27
|
"typescript": "^6.0.3",
|
|
28
28
|
"vitest": "^4.1.9",
|
|
29
|
-
"@cat-factory/server": "0.106.
|
|
30
|
-
"@cat-factory/spend": "0.12.
|
|
29
|
+
"@cat-factory/server": "0.106.1",
|
|
30
|
+
"@cat-factory/spend": "0.12.1"
|
|
31
31
|
},
|
|
32
32
|
"scripts": {
|
|
33
33
|
"build": "tsc -p tsconfig.json",
|
package/src/server.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { handleInline } from './inline.js'
|
|
|
6
6
|
import { redactSecrets } from './git.js'
|
|
7
7
|
import { JobRegistry, loadRunnerLimits, type JobResultBase, type RunOptions } from './runner.js'
|
|
8
8
|
import { log } from './logger.js'
|
|
9
|
+
import { HARNESS_VERSION } from './version.js'
|
|
9
10
|
|
|
10
11
|
// The container's HTTP entry point. The Worker addresses one instance per run and
|
|
11
12
|
// POSTs a job to /jobs (the body's `kind` selects which agent runs); the harness
|
|
@@ -109,7 +110,14 @@ function send(res: ServerResponse, status: number, body: unknown): void {
|
|
|
109
110
|
const server = createServer((req, res) => {
|
|
110
111
|
void (async () => {
|
|
111
112
|
if (req.method === 'GET' && req.url === '/health') {
|
|
112
|
-
|
|
113
|
+
// Report the harness version so a backend can detect a stale/mismatched executor and
|
|
114
|
+
// fail loudly early (see version.ts). Unauthenticated like the rest of /health — the
|
|
115
|
+
// version is not a secret. An old image predating this field simply omits it, which the
|
|
116
|
+
// backend treats as a stale signal.
|
|
117
|
+
return send(res, 200, {
|
|
118
|
+
status: 'ok',
|
|
119
|
+
...(HARNESS_VERSION ? { version: HARNESS_VERSION } : {}),
|
|
120
|
+
})
|
|
113
121
|
}
|
|
114
122
|
// All non-health endpoints are gated by the optional shared secret.
|
|
115
123
|
if (!authorized(req)) {
|
package/src/version.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs'
|
|
2
|
+
import { dirname, join } from 'node:path'
|
|
3
|
+
import { fileURLToPath } from 'node:url'
|
|
4
|
+
|
|
5
|
+
// The harness's OWN version, resolved once at module load and reported on `/health`. A
|
|
6
|
+
// backend reads it back over the health handshake to detect a STALE or MISMATCHED executor
|
|
7
|
+
// (an old image left behind by a mutable tag, an outdated native install) and fail loudly
|
|
8
|
+
// and early — instead of the cryptic downstream symptom a version skew otherwise produces
|
|
9
|
+
// (e.g. a since-removed git flag reappearing and breaking every authenticated clone/push).
|
|
10
|
+
//
|
|
11
|
+
// Resolution order, most authoritative first:
|
|
12
|
+
// 1. `HARNESS_VERSION` env — the Docker image can bake it; an operator can override.
|
|
13
|
+
// 2. the version file the image writes next to `dist/` (the image deliberately ships NO
|
|
14
|
+
// package.json, so it captures the version into `harness-version.txt` at build time).
|
|
15
|
+
// 3. `package.json` — the native/npm install and a source checkout both keep it beside
|
|
16
|
+
// `dist/`, so `dist/version.js` finds it one level up.
|
|
17
|
+
// Undefined only for an oddly-assembled runtime carrying none of the three; the backend then
|
|
18
|
+
// treats "no reported version" as a strong stale signal in its own right.
|
|
19
|
+
|
|
20
|
+
function readVersionFile(dir: string, rel: string): string | undefined {
|
|
21
|
+
try {
|
|
22
|
+
const raw = readFileSync(join(dir, rel), 'utf8')
|
|
23
|
+
const value = rel.endsWith('.json') ? (JSON.parse(raw) as { version?: string }).version : raw
|
|
24
|
+
const trimmed = value?.trim()
|
|
25
|
+
return trimmed || undefined
|
|
26
|
+
} catch {
|
|
27
|
+
return undefined
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function resolveHarnessVersion(): string | undefined {
|
|
32
|
+
const fromEnv = process.env.HARNESS_VERSION?.trim()
|
|
33
|
+
if (fromEnv) return fromEnv
|
|
34
|
+
let dir: string
|
|
35
|
+
try {
|
|
36
|
+
// Compiled to `dist/version.js`; the baked file / package.json sit one level up from dist.
|
|
37
|
+
dir = dirname(fileURLToPath(import.meta.url))
|
|
38
|
+
} catch {
|
|
39
|
+
return undefined
|
|
40
|
+
}
|
|
41
|
+
return readVersionFile(dir, '../harness-version.txt') ?? readVersionFile(dir, '../package.json')
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** The running harness version, or undefined when it cannot be determined. */
|
|
45
|
+
export const HARNESS_VERSION: string | undefined = resolveHarnessVersion()
|