@cat-factory/executor-harness 1.145.1 → 1.147.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/README.md +52 -7
- package/dist/agent.js +5 -2
- package/dist/docker-capability.d.ts +99 -0
- package/dist/docker-capability.js +351 -0
- package/dist/docker-command.d.ts +30 -0
- package/dist/docker-command.js +91 -0
- package/dist/docker-probe-image.d.ts +36 -0
- package/dist/docker-probe-image.js +148 -0
- package/dist/docker-status.d.ts +85 -18
- package/dist/docker-status.js +93 -36
- package/dist/environment-inventory.d.ts +51 -6
- package/dist/environment-inventory.js +75 -18
- package/dist/harness-server.js +7 -1
- package/dist/infra-standup.d.ts +15 -12
- package/dist/infra-standup.js +58 -23
- package/dist/job.d.ts +10 -0
- package/package.json +5 -5
- package/src/agent.ts +5 -2
- package/src/docker-capability.ts +518 -0
- package/src/docker-command.ts +117 -0
- package/src/docker-probe-image.ts +171 -0
- package/src/docker-status.ts +134 -37
- package/src/environment-inventory.ts +129 -31
- package/src/harness-server.ts +7 -1
- package/src/infra-standup.ts +61 -23
- package/src/job.ts +10 -0
|
@@ -2,6 +2,7 @@ import { execFile } from 'node:child_process';
|
|
|
2
2
|
import { promisify } from 'node:util';
|
|
3
3
|
import { log } from './logger.js';
|
|
4
4
|
import { harnessListenPort } from './harness-port.js';
|
|
5
|
+
import { probeDockerWorkload } from './docker-capability.js';
|
|
5
6
|
// ---------------------------------------------------------------------------
|
|
6
7
|
// What this machine actually has, probed ONCE per job and stated to the agent.
|
|
7
8
|
//
|
|
@@ -31,6 +32,12 @@ import { harnessListenPort } from './harness-port.js';
|
|
|
31
32
|
// a daemon this machine is CONFIGURED for but which has not answered yet is a fourth state
|
|
32
33
|
// that resolves to `unknown`, never to the absence a refused connection looks like: the
|
|
33
34
|
// image's daemon is started in the background and the job begins before it is ready.
|
|
35
|
+
// - A daemon that ANSWERS is still not a daemon that WORKS, which is the same mistake one level
|
|
36
|
+
// in. A rootless daemon nested in a sandbox serves while its snapshotter cannot mount any
|
|
37
|
+
// image layer, so `docker info` succeeds and `docker build` / `docker run` / `docker pull`
|
|
38
|
+
// all fail (issue #2120). Only a container that RAN settles that, so the reachable case is
|
|
39
|
+
// split by a real workload (docker-capability.ts) into `usable`, `unusable`, and a daemon
|
|
40
|
+
// that answered while the check itself could not be carried out.
|
|
34
41
|
//
|
|
35
42
|
// Deliberately NOT here: the agent's own tools (web search, file tools, MCP servers). Those are
|
|
36
43
|
// the CLI's, they differ per harness, and each is already stated where it is true. Claiming one
|
|
@@ -260,10 +267,33 @@ export async function probeEnvironment(run, opts = {}) {
|
|
|
260
267
|
showVersion: probe.showVersion,
|
|
261
268
|
presence: toolPresence(await run(probe.command, probe.args)),
|
|
262
269
|
}))),
|
|
263
|
-
|
|
270
|
+
probeDockerCapability(run, opts),
|
|
264
271
|
]);
|
|
265
272
|
return { tools, dockerDaemon, harnessPort: opts.harnessPort ?? harnessListenPort() };
|
|
266
273
|
}
|
|
274
|
+
/**
|
|
275
|
+
* The daemon's full answer: whether one is reachable, and then whether it can run a container.
|
|
276
|
+
*
|
|
277
|
+
* The two steps are kept apart because they fail for unrelated reasons and only the FIRST has a
|
|
278
|
+
* cheap answer. A daemon nobody can reach has no workload to run, so the check that costs a
|
|
279
|
+
* container start is asked only where there is something to ask it of; a daemon that answered
|
|
280
|
+
* carries its server version into every one of the three states that follow it, because the
|
|
281
|
+
* agent reading the line is entitled to know which daemon the verdict is about.
|
|
282
|
+
*/
|
|
283
|
+
async function probeDockerCapability(run, opts) {
|
|
284
|
+
const daemon = await probeDockerDaemon(run, opts);
|
|
285
|
+
if (daemon.status === 'absent')
|
|
286
|
+
return { status: 'absent' };
|
|
287
|
+
if (daemon.status === 'unknown')
|
|
288
|
+
return { status: 'unknown', reason: daemon.reason };
|
|
289
|
+
const server = daemon.version ? { server: daemon.version } : {};
|
|
290
|
+
const workload = await (opts.workload ?? probeDockerWorkload)(opts.signal);
|
|
291
|
+
if (workload.status === 'usable')
|
|
292
|
+
return { status: 'usable', ...server };
|
|
293
|
+
if (workload.status === 'unusable')
|
|
294
|
+
return { status: 'unusable', ...server, detail: workload.detail };
|
|
295
|
+
return { status: 'serving', ...server, reason: workload.reason };
|
|
296
|
+
}
|
|
267
297
|
/**
|
|
268
298
|
* Ask the daemon itself, and do not mistake a daemon that is STARTING for one that is not there.
|
|
269
299
|
*
|
|
@@ -377,20 +407,46 @@ function harnessPortLine(port) {
|
|
|
377
407
|
'check aimed at it passes without your service ever having run. Bind anything you start ' +
|
|
378
408
|
'somewhere else.');
|
|
379
409
|
}
|
|
380
|
-
/**
|
|
410
|
+
/**
|
|
411
|
+
* The Docker line, which says something different in each of the five cases, and is TOTAL over
|
|
412
|
+
* them: adding a state without deciding what an agent should do about it stops the build.
|
|
413
|
+
*
|
|
414
|
+
* Only `usable` may claim the commands work, and it may only be reached by having RUN one. The
|
|
415
|
+
* line that used to stand here made that claim off `docker info` alone, which is how every agent
|
|
416
|
+
* in a run was told, as fact, that a daemon which could not mount a single image layer would
|
|
417
|
+
* build and run one.
|
|
418
|
+
*/
|
|
381
419
|
function dockerDaemonLine(daemon) {
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
420
|
+
const server = 'server' in daemon && daemon.server ? ` (server ${daemon.server})` : '';
|
|
421
|
+
switch (daemon.status) {
|
|
422
|
+
case 'usable':
|
|
423
|
+
return (`A Docker daemon is reachable${server} and the platform ran a container on it: ` +
|
|
424
|
+
'`docker build`, `docker run` and `docker compose up` work here.');
|
|
425
|
+
case 'unusable':
|
|
426
|
+
return (`A Docker daemon is reachable${server} but it CANNOT run a container: the platform ` +
|
|
427
|
+
`built a one-layer image and tried to run it here, and that failed (${daemon.detail}). ` +
|
|
428
|
+
'`docker build`, `docker run`, `docker pull` of a multi-layer image and ' +
|
|
429
|
+
'`docker compose up` all fail for the same reason, so there is nothing to retry and no ' +
|
|
430
|
+
'flag that works around it. Produce the Dockerfile or compose file you were asked for, ' +
|
|
431
|
+
'say in one line that it could not be built or run here, and move on.');
|
|
432
|
+
case 'serving':
|
|
433
|
+
return (`A Docker daemon is reachable${server}, but whether it can actually build or run an ` +
|
|
434
|
+
`image was NOT established (${daemon.reason}). Reaching the daemon is not the same fact: ` +
|
|
435
|
+
'a sandboxed one answers while being unable to mount any image layer. Try it if you need ' +
|
|
436
|
+
'it, and do not read a failure as a defect in the work.');
|
|
437
|
+
case 'unknown':
|
|
438
|
+
return ('Whether a Docker daemon is reachable could not be determined ' +
|
|
439
|
+
`(${daemon.reason}): try it if you need it, and do not read a failure as a defect in the work.`);
|
|
440
|
+
case 'absent':
|
|
441
|
+
return ('NO Docker daemon is reachable: `docker build`, `docker run` and `docker compose up` ' +
|
|
442
|
+
'will fail here whatever the CLI reports. Produce the Dockerfile or compose file you ' +
|
|
443
|
+
'were asked for, say in one line that you could not build it here, and move on.');
|
|
444
|
+
default:
|
|
445
|
+
return unnamedCapability(daemon);
|
|
390
446
|
}
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
447
|
+
}
|
|
448
|
+
function unnamedCapability(daemon) {
|
|
449
|
+
return `Whether a Docker daemon is reachable could not be determined (the platform reported an unrecognised verdict ${JSON.stringify(daemon)}): try it if you need it.`;
|
|
394
450
|
}
|
|
395
451
|
/**
|
|
396
452
|
* Probe the machine and fold the inventory onto `systemPrompt`. THE composition point: the harness
|
|
@@ -408,12 +464,13 @@ function dockerDaemonLine(daemon) {
|
|
|
408
464
|
*/
|
|
409
465
|
export async function appendEnvironmentInventory(systemPrompt, opts = {}) {
|
|
410
466
|
const logger = opts.log ?? log;
|
|
467
|
+
// Everything that is not this function's OWN is forwarded by construction, rather than key by
|
|
468
|
+
// key. The list of copied keys silently dropped `workload`, whose whole point is that a suite
|
|
469
|
+
// can inject one: a test driving THIS entry point (the only one `handleAgent` uses) got the
|
|
470
|
+
// real probe instead, which starts a container on whatever machine the suite runs on.
|
|
471
|
+
const { log: _log, run, ...probeOptions } = opts;
|
|
411
472
|
try {
|
|
412
|
-
const inventory = await probeEnvironment(
|
|
413
|
-
...(opts.sleep ? { sleep: opts.sleep } : {}),
|
|
414
|
-
...(opts.daemonExpected === undefined ? {} : { daemonExpected: opts.daemonExpected }),
|
|
415
|
-
...(opts.harnessPort === undefined ? {} : { harnessPort: opts.harnessPort }),
|
|
416
|
-
});
|
|
473
|
+
const inventory = await probeEnvironment(run ?? spawnProbeRunner(opts.signal), probeOptions);
|
|
417
474
|
logger.info('agent: probed the environment', {
|
|
418
475
|
installed: inventory.tools
|
|
419
476
|
.filter((t) => t.presence.status === 'present')
|
package/dist/harness-server.js
CHANGED
|
@@ -6,6 +6,7 @@ import { handleAgent } from './agent.js';
|
|
|
6
6
|
import { handleInline } from './inline.js';
|
|
7
7
|
import { redactSecrets } from './git.js';
|
|
8
8
|
import { readDockerStatus } from './docker-status.js';
|
|
9
|
+
import { reportedDockerWorkload } from './docker-capability.js';
|
|
9
10
|
import { harnessListenPort } from './harness-port.js';
|
|
10
11
|
import { JobRegistry, loadRunnerLimits } from './runner.js';
|
|
11
12
|
import { log } from './logger.js';
|
|
@@ -122,11 +123,16 @@ const server = createServer((req, res) => {
|
|
|
122
123
|
// would spawn a process per poll to answer a question this endpoint is not the one to act
|
|
123
124
|
// on; the stand-up re-confirms a recorded absence at the moment it matters
|
|
124
125
|
// (`resolveDockerVerdict`), so a stale negative here never becomes a stale refusal there.
|
|
126
|
+
//
|
|
127
|
+
// `workload` is the other half, and the reason the block used to mislead: what the record
|
|
128
|
+
// says is `serving`, and serving is not usable. It reports the last measurement any job
|
|
129
|
+
// took (docker-capability.ts) and NEVER takes one itself, for the same polling reason,
|
|
130
|
+
// which is why `unmeasured` is one of the words it can answer.
|
|
125
131
|
return send(res, 200, {
|
|
126
132
|
status: 'ok',
|
|
127
133
|
...(HARNESS_VERSION ? { version: HARNESS_VERSION } : {}),
|
|
128
134
|
capabilities: HARNESS_BODY_CAPABILITIES,
|
|
129
|
-
docker: await readDockerStatus(),
|
|
135
|
+
docker: { ...(await readDockerStatus()), workload: reportedDockerWorkload() },
|
|
130
136
|
});
|
|
131
137
|
}
|
|
132
138
|
// All non-health endpoints are gated by the optional shared secret.
|
package/dist/infra-standup.d.ts
CHANGED
|
@@ -9,19 +9,22 @@ import type { Logger } from './logger.js';
|
|
|
9
9
|
* still run unit-level tests and report what it could. A no-op for ephemeral / no-infra /
|
|
10
10
|
* no-compose-path runs.
|
|
11
11
|
*
|
|
12
|
-
* A CONFIRMED absence of a Docker daemon short-circuits it: the container
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* looking like a Tester that simply chose not to. Anything OTHER than a confirmed
|
|
18
|
-
* attempts as before (`DockerStatus.available` in docker-status.ts states why
|
|
19
|
-
* own value).
|
|
12
|
+
* A CONFIRMED absence of a USABLE Docker daemon short-circuits it: the container already knows
|
|
13
|
+
* compose cannot work, so running it would only turn a fact this container holds into an error
|
|
14
|
+
* the agent has to interpret. The record then carries the stated cause plus the two facts that
|
|
15
|
+
* decide where a human should look (`dockerAvailable`: was anything answering, `dockerWorkload`:
|
|
16
|
+
* what a container did on it), which is what makes the Tester step say why it ran no infra
|
|
17
|
+
* instead of looking like a Tester that simply chose not to. Anything OTHER than a confirmed
|
|
18
|
+
* negative attempts as before (`DockerStatus.available` in docker-status.ts states why
|
|
19
|
+
* "undecided" is its own value).
|
|
20
20
|
*
|
|
21
|
-
* "Confirmed", not merely recorded: {@link resolveDockerVerdict} re-checks
|
|
22
|
-
* against a live daemon first, so a warm-pool container whose sidecar came up late is not
|
|
23
|
-
*
|
|
24
|
-
*
|
|
21
|
+
* "Confirmed", not merely recorded: {@link resolveDockerVerdict} re-checks the boot record
|
|
22
|
+
* against a live daemon first, so a warm-pool container whose sidecar came up late is not latched
|
|
23
|
+
* into refusing infra that works. It re-checks a recorded PRESENCE too, by running an actual
|
|
24
|
+
* container: a rootless daemon in a sandbox answers `docker version` while being unable to mount
|
|
25
|
+
* an image, and compose against that one died on a mount error inside the very mechanism that
|
|
26
|
+
* exists to explain why infra did not come up. `probe` is that check, injected so the unit suite
|
|
27
|
+
* can state every answer on a machine that has its own daemon either way.
|
|
25
28
|
*
|
|
26
29
|
* Whether it succeeds or fails, the (redacted, bounded) command output is captured into a
|
|
27
30
|
* {@link InfraSetupRecord} returned alongside the prompt `note`, so the backend can surface
|
package/dist/infra-standup.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// guarantees the matching teardown. `manageInfra` is the one entry point a mode calls.
|
|
5
5
|
import { execFile } from 'node:child_process';
|
|
6
6
|
import { promisify } from 'node:util';
|
|
7
|
-
import {
|
|
7
|
+
import { probeLiveDockerCapability, readDockerStatus, resolveDockerVerdict, } from './docker-status.js';
|
|
8
8
|
import { standUpFrontend, tearDownFrontend } from './frontend-infra.js';
|
|
9
9
|
import { captureRedactedOutput, redactSecrets } from './redact.js';
|
|
10
10
|
const exec = promisify(execFile);
|
|
@@ -15,19 +15,22 @@ const exec = promisify(execFile);
|
|
|
15
15
|
* still run unit-level tests and report what it could. A no-op for ephemeral / no-infra /
|
|
16
16
|
* no-compose-path runs.
|
|
17
17
|
*
|
|
18
|
-
* A CONFIRMED absence of a Docker daemon short-circuits it: the container
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* looking like a Tester that simply chose not to. Anything OTHER than a confirmed
|
|
24
|
-
* attempts as before (`DockerStatus.available` in docker-status.ts states why
|
|
25
|
-
* own value).
|
|
18
|
+
* A CONFIRMED absence of a USABLE Docker daemon short-circuits it: the container already knows
|
|
19
|
+
* compose cannot work, so running it would only turn a fact this container holds into an error
|
|
20
|
+
* the agent has to interpret. The record then carries the stated cause plus the two facts that
|
|
21
|
+
* decide where a human should look (`dockerAvailable`: was anything answering, `dockerWorkload`:
|
|
22
|
+
* what a container did on it), which is what makes the Tester step say why it ran no infra
|
|
23
|
+
* instead of looking like a Tester that simply chose not to. Anything OTHER than a confirmed
|
|
24
|
+
* negative attempts as before (`DockerStatus.available` in docker-status.ts states why
|
|
25
|
+
* "undecided" is its own value).
|
|
26
26
|
*
|
|
27
|
-
* "Confirmed", not merely recorded: {@link resolveDockerVerdict} re-checks
|
|
28
|
-
* against a live daemon first, so a warm-pool container whose sidecar came up late is not
|
|
29
|
-
*
|
|
30
|
-
*
|
|
27
|
+
* "Confirmed", not merely recorded: {@link resolveDockerVerdict} re-checks the boot record
|
|
28
|
+
* against a live daemon first, so a warm-pool container whose sidecar came up late is not latched
|
|
29
|
+
* into refusing infra that works. It re-checks a recorded PRESENCE too, by running an actual
|
|
30
|
+
* container: a rootless daemon in a sandbox answers `docker version` while being unable to mount
|
|
31
|
+
* an image, and compose against that one died on a mount error inside the very mechanism that
|
|
32
|
+
* exists to explain why infra did not come up. `probe` is that check, injected so the unit suite
|
|
33
|
+
* can state every answer on a machine that has its own daemon either way.
|
|
31
34
|
*
|
|
32
35
|
* Whether it succeeds or fails, the (redacted, bounded) command output is captured into a
|
|
33
36
|
* {@link InfraSetupRecord} returned alongside the prompt `note`, so the backend can surface
|
|
@@ -38,26 +41,42 @@ const exec = promisify(execFile);
|
|
|
38
41
|
* this container makes about itself, and the acceptance suite can only exercise it on a machine
|
|
39
42
|
* where the daemon genuinely fails.
|
|
40
43
|
*/
|
|
41
|
-
export async function standUpInfra(dir, infra, signal, logger, probe =
|
|
44
|
+
export async function standUpInfra(dir, infra, signal, logger, probe = probeLiveDockerCapability) {
|
|
42
45
|
if (infra.environment !== 'local' || infra.noInfraDependencies || !infra.composePath) {
|
|
43
46
|
return { started: false };
|
|
44
47
|
}
|
|
45
48
|
const startedAt = Date.now();
|
|
46
49
|
const recorded = await readDockerStatus();
|
|
47
|
-
const docker = await resolveDockerVerdict(recorded,
|
|
50
|
+
const docker = await resolveDockerVerdict(recorded, {
|
|
51
|
+
probe,
|
|
52
|
+
...(signal ? { signal } : {}),
|
|
53
|
+
logger,
|
|
54
|
+
});
|
|
48
55
|
if (docker.refusal) {
|
|
49
56
|
const note = `the dependencies could not be started: ${docker.refusal}`;
|
|
50
|
-
logger.warn('agent(explore): infra stand-up refused, no docker daemon', {
|
|
57
|
+
logger.warn('agent(explore): infra stand-up refused, no usable docker daemon', {
|
|
51
58
|
composePath: infra.composePath,
|
|
52
59
|
dockerSource: recorded.source,
|
|
53
60
|
dockerReason: recorded.reason,
|
|
61
|
+
// What the LIVE check found, which is the only place the second refusal cause exists: the
|
|
62
|
+
// boot record's own words for a daemon that answers and cannot run anything are `serving`
|
|
63
|
+
// and nothing else, so a log line carrying the record alone describes the wrong failure.
|
|
64
|
+
dockerWorkload: docker.workload?.status ?? 'unmeasured',
|
|
65
|
+
...(docker.workload?.status === 'unusable' ? { dockerDetail: docker.workload.detail } : {}),
|
|
54
66
|
});
|
|
55
67
|
return {
|
|
56
68
|
started: false,
|
|
57
69
|
note,
|
|
58
70
|
record: {
|
|
59
71
|
started: false,
|
|
60
|
-
|
|
72
|
+
// NOT a flat `false`. Two refusals reach this branch and they have opposite fixes: with
|
|
73
|
+
// nothing answering, the executor image or the sandbox running it is what to go and look
|
|
74
|
+
// at; with a daemon that answers and cannot run a container, that daemon is up and an
|
|
75
|
+
// operator sent to restart it finds nothing wrong. `dockerAvailable` answers only the
|
|
76
|
+
// first question and `dockerWorkload` the second, so neither has to carry the other's
|
|
77
|
+
// fact (the same rule the compose-failure branch below states for its own `false`).
|
|
78
|
+
dockerAvailable: docker.daemon === true,
|
|
79
|
+
...workloadRecord(docker.workload),
|
|
61
80
|
composePath: infra.composePath,
|
|
62
81
|
at: Date.now(),
|
|
63
82
|
durationMs: Date.now() - startedAt,
|
|
@@ -76,6 +95,7 @@ export async function standUpInfra(dir, infra, signal, logger, probe = probeDock
|
|
|
76
95
|
record: {
|
|
77
96
|
started: true,
|
|
78
97
|
dockerAvailable: true,
|
|
98
|
+
...workloadRecord(docker.workload),
|
|
79
99
|
composePath: infra.composePath,
|
|
80
100
|
at: Date.now(),
|
|
81
101
|
durationMs: Date.now() - startedAt,
|
|
@@ -96,12 +116,13 @@ export async function standUpInfra(dir, infra, signal, logger, probe = probeDock
|
|
|
96
116
|
note,
|
|
97
117
|
record: {
|
|
98
118
|
started: false,
|
|
99
|
-
// A compose failure with a REACHABLE daemon
|
|
100
|
-
//
|
|
101
|
-
//
|
|
102
|
-
//
|
|
103
|
-
//
|
|
104
|
-
...(docker.
|
|
119
|
+
// A compose failure with a REACHABLE daemon, which is a third diagnosis again: the stack
|
|
120
|
+
// itself did not come up. Read off the RESOLVED verdict, so a container whose daemon came
|
|
121
|
+
// up after boot claims the daemon it actually reached rather than the one its boot record
|
|
122
|
+
// still denies, and OMITTED rather than `false` when nothing answered the live check,
|
|
123
|
+
// because the boot record's word for that is a hypothesis and not a measurement.
|
|
124
|
+
...(docker.daemon === true ? { dockerAvailable: true } : {}),
|
|
125
|
+
...workloadRecord(docker.workload),
|
|
105
126
|
composePath: infra.composePath,
|
|
106
127
|
at: Date.now(),
|
|
107
128
|
durationMs: Date.now() - startedAt,
|
|
@@ -111,6 +132,20 @@ export async function standUpInfra(dir, infra, signal, logger, probe = probeDock
|
|
|
111
132
|
};
|
|
112
133
|
}
|
|
113
134
|
}
|
|
135
|
+
/**
|
|
136
|
+
* What the live check measured, for the record the Tester step shows.
|
|
137
|
+
*
|
|
138
|
+
* Its own field beside `dockerAvailable` because the two answer different questions and only one
|
|
139
|
+
* of them has a boolean's worth of answers: a daemon either answered or it did not, while what a
|
|
140
|
+
* container DID on it is `usable`, `unusable`, or a check that could not be carried out. Absent
|
|
141
|
+
* when nothing was measured at all (an undecided boot record probes nothing), which is not the
|
|
142
|
+
* same as a check that ran and could not tell.
|
|
143
|
+
*/
|
|
144
|
+
function workloadRecord(workload) {
|
|
145
|
+
if (!workload)
|
|
146
|
+
return {};
|
|
147
|
+
return { dockerWorkload: workload.status === 'unknown' ? 'undetermined' : workload.status };
|
|
148
|
+
}
|
|
114
149
|
/**
|
|
115
150
|
* Stand the run's infra up and return a single cleanup handle, dispatching on the spec's
|
|
116
151
|
* `kind`: the frontend UI-test flow (`kind: 'frontend'`) builds/serves the app + WireMock as
|
package/dist/job.d.ts
CHANGED
|
@@ -556,6 +556,16 @@ export interface InfraSetupRecord {
|
|
|
556
556
|
* mistake that let a daemon-less image read as an ordinary infra failure for months.
|
|
557
557
|
*/
|
|
558
558
|
dockerAvailable?: boolean;
|
|
559
|
+
/**
|
|
560
|
+
* What a real container DID on that daemon, when the platform measured it.
|
|
561
|
+
*
|
|
562
|
+
* The third diagnosis, and the one `dockerAvailable` structurally cannot carry: a rootless
|
|
563
|
+
* daemon nested in a sandbox answers throughout while unable to mount any image layer, so it is
|
|
564
|
+
* `dockerAvailable: true` and no stack can come up on it (issue #2120). Reporting that as an
|
|
565
|
+
* absent daemon sends a human to restart one that is already up. `undetermined` is a check that
|
|
566
|
+
* ran and could not tell; ABSENT means nothing was measured at all.
|
|
567
|
+
*/
|
|
568
|
+
dockerWorkload?: 'usable' | 'unusable' | 'undetermined';
|
|
559
569
|
/** The repo-relative compose file that was stood up. */
|
|
560
570
|
composePath?: string;
|
|
561
571
|
/** Epoch ms the stand-up attempt finished. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cat-factory/executor-harness",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.147.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",
|
|
@@ -25,10 +25,10 @@
|
|
|
25
25
|
"access": "public"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
|
-
"@cat-factory/contracts": "0.
|
|
29
|
-
"@cat-factory/kernel": "0.
|
|
30
|
-
"@cat-factory/server": "0.
|
|
31
|
-
"@cat-factory/spend": "0.
|
|
28
|
+
"@cat-factory/contracts": "0.338.0",
|
|
29
|
+
"@cat-factory/kernel": "0.327.0",
|
|
30
|
+
"@cat-factory/server": "0.310.1",
|
|
31
|
+
"@cat-factory/spend": "0.17.3",
|
|
32
32
|
"@hono/node-server": "^2.1.1",
|
|
33
33
|
"@types/node": "^26.4.0",
|
|
34
34
|
"hono": "^4.13.5",
|
package/src/agent.ts
CHANGED
|
@@ -187,8 +187,11 @@ export async function handleAgent(job: AgentJob, opts: RunOptions = {}): Promise
|
|
|
187
187
|
// This sits on the critical path AHEAD of the clone, which is the cost of having one
|
|
188
188
|
// composition point instead of one per mode (each mode owns its own clone, so there is no
|
|
189
189
|
// single post-clone place to put this). The pass is sized for that: everything in it runs
|
|
190
|
-
// concurrently, every probe
|
|
191
|
-
// deliberate
|
|
190
|
+
// concurrently, and every probe either answers in milliseconds or is bounded. Two of them are
|
|
191
|
+
// deliberate waits rather than instant answers: one short retry for a daemon that is still
|
|
192
|
+
// starting, and, only once a daemon has answered, the container the platform runs to find out
|
|
193
|
+
// whether it can run one at all (`docker-capability.ts`, budgeted and memoised per container
|
|
194
|
+
// for a positive). Both take the job's signal, so an abandoned run stops paying at once.
|
|
192
195
|
|
|
193
196
|
const staged: AgentJob = {
|
|
194
197
|
...job,
|