@cat-factory/executor-harness 1.35.0 → 1.37.2
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/git.js +12 -8
- package/dist/inline.js +58 -0
- package/dist/job.js +29 -0
- package/dist/server.js +9 -1
- package/package.json +3 -3
- package/src/git.ts +12 -8
- package/src/inline.ts +63 -0
- package/src/job.ts +67 -0
- package/src/server.ts +9 -1
package/dist/git.js
CHANGED
|
@@ -51,14 +51,18 @@ const GIT_TIMEOUT_MS = Math.max(GIT_TIMEOUT_FLOOR_MS, loadRunnerLimits().inactiv
|
|
|
51
51
|
// Emptying the helper list (`credential.helper=` with no value RESETS the multi-valued config,
|
|
52
52
|
// dropping the system/global/local helpers) removes GCM from the chain, so git falls back to
|
|
53
53
|
// the harness's own askpass helper — which returns the per-job PAT we already hold (see
|
|
54
|
-
// `authEnv`).
|
|
55
|
-
//
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
54
|
+
// `authEnv`). The token is never in argv; only this non-secret config is.
|
|
55
|
+
//
|
|
56
|
+
// DO NOT re-add `-c credential.interactive=false` here. It reads like harmless belt-and-braces
|
|
57
|
+
// but modern git (≥ 2.47, incl. the executor image + host git) HONORS `credential.interactive`
|
|
58
|
+
// and treats invoking GIT_ASKPASS as "interactive" — so with it set git SKIPS the askpass
|
|
59
|
+
// entirely and dies with "fatal: unable to get password from user", failing EVERY authenticated
|
|
60
|
+
// clone/push on both the native and container paths (it clones a public base repo fine — that
|
|
61
|
+
// needs no auth — then fails only at push, which is what makes it look intermittent). The GCM
|
|
62
|
+
// popup it was meant to belt-and-braces against is already fully handled by the emptied helper
|
|
63
|
+
// list above plus `GIT_TERMINAL_PROMPT=0` / `GCM_INTERACTIVE=never` in the env (see authEnv /
|
|
64
|
+
// nonInteractiveGitEnv). Exported so a unit test can pin that this arg never creeps back in.
|
|
65
|
+
export const NON_INTERACTIVE_CREDENTIAL_ARGS = ['-c', 'credential.helper='];
|
|
62
66
|
/**
|
|
63
67
|
* Env applied to git commands that DON'T carry {@link authEnv} (local ops like config/checkout/
|
|
64
68
|
* rev-parse). Keeps them from ever going interactive too — `GIT_TERMINAL_PROMPT=0` blocks the
|
package/dist/inline.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { mkdtemp, rm } from 'node:fs/promises';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { runSubscriptionHarness } from './agent-runner.js';
|
|
5
|
+
// The `inline` job handler: one-shot LLM completion through a subscription harness CLI
|
|
6
|
+
// (Claude Code / Codex) with NO checkout. It is the container analogue of the local
|
|
7
|
+
// host-CLI inline runner (runtimes/local `harnessInline.ts`) — it exists so the inline
|
|
8
|
+
// LLM steps (requirements reviewer, brainstorm, task-estimator, inline document kinds) can
|
|
9
|
+
// run on a subscription model even when the host has no `claude`/`codex` binary (and in
|
|
10
|
+
// mothership mode without touching the host), at warm-pool latency. It reuses
|
|
11
|
+
// `runSubscriptionHarness`'s credential-env setup verbatim (the single site that turns a
|
|
12
|
+
// leased subscription token into `CLAUDE_CODE_OAUTH_TOKEN` / `ANTHROPIC_*` / a Codex
|
|
13
|
+
// `auth.json`), so the container and coding paths can never disagree on how a credential
|
|
14
|
+
// is injected.
|
|
15
|
+
/**
|
|
16
|
+
* Map the harness CLI's terminal stop reason (lifted onto the last call metric) to the
|
|
17
|
+
* inline `finishReason` the reviewer keys off. Only Claude Code reports it (`max_tokens` on
|
|
18
|
+
* a `--output-format stream-json` result); Codex's thinner stream exposes none, so it reads
|
|
19
|
+
* as `stop` — the same one-shot limitation the host-CLI runner has.
|
|
20
|
+
*/
|
|
21
|
+
function deriveFinishReason(calls) {
|
|
22
|
+
const last = calls?.[calls.length - 1];
|
|
23
|
+
const reason = last?.finishReason?.toLowerCase() ?? '';
|
|
24
|
+
return reason === 'max_tokens' || reason === 'length' ? 'length' : 'stop';
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Run one inline completion in a throwaway temp cwd and return the reply text + lifted
|
|
28
|
+
* usage/telemetry. The CLI clones/pushes nothing — the empty cwd only gives it a working
|
|
29
|
+
* directory. The job's watchdog (inactivity + max-duration, see {@link JobRegistry}) bounds
|
|
30
|
+
* it through `opts.signal`; `opts.onActivity` keeps the inactivity timer alive while the CLI
|
|
31
|
+
* streams. The temp cwd is always removed.
|
|
32
|
+
*/
|
|
33
|
+
export async function handleInline(job, opts) {
|
|
34
|
+
opts.onPhase?.('agent');
|
|
35
|
+
const cwd = await mkdtemp(join(tmpdir(), 'cf-inline-'));
|
|
36
|
+
try {
|
|
37
|
+
const outcome = await runSubscriptionHarness(job.harness, {
|
|
38
|
+
cwd,
|
|
39
|
+
model: job.model,
|
|
40
|
+
systemPrompt: job.systemPrompt,
|
|
41
|
+
userPrompt: job.userPrompt,
|
|
42
|
+
...(job.subscriptionToken ? { subscriptionToken: job.subscriptionToken } : {}),
|
|
43
|
+
...(job.subscriptionBaseUrl ? { subscriptionBaseUrl: job.subscriptionBaseUrl } : {}),
|
|
44
|
+
...(job.ambientAuth ? { ambientAuth: true } : {}),
|
|
45
|
+
...(opts.signal ? { signal: opts.signal } : {}),
|
|
46
|
+
...(opts.onActivity ? { onActivity: opts.onActivity } : {}),
|
|
47
|
+
});
|
|
48
|
+
return {
|
|
49
|
+
text: outcome.summary,
|
|
50
|
+
finishReason: deriveFinishReason(outcome.callMetrics),
|
|
51
|
+
...(outcome.usage ? { usage: outcome.usage } : {}),
|
|
52
|
+
...(outcome.callMetrics ? { callMetrics: outcome.callMetrics } : {}),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
await rm(cwd, { recursive: true, force: true }).catch(() => { });
|
|
57
|
+
}
|
|
58
|
+
}
|
package/dist/job.js
CHANGED
|
@@ -455,6 +455,35 @@ function parseFrontendInfraSpec(o) {
|
|
|
455
455
|
...(wiremockPort !== undefined ? { wiremockPort } : {}),
|
|
456
456
|
};
|
|
457
457
|
}
|
|
458
|
+
/**
|
|
459
|
+
* Validate + narrow an untrusted body into an {@link InlineJob}. The harness MUST be a
|
|
460
|
+
* subscription harness (`claude-code` / `codex`) — the inline path never runs Pi (that goes
|
|
461
|
+
* through the LLM proxy inline, not a container CLI). Reuses {@link parseHarnessAuth}, so a
|
|
462
|
+
* non-ambient job requires `subscriptionToken`.
|
|
463
|
+
*/
|
|
464
|
+
export function parseInlineJob(input) {
|
|
465
|
+
if (typeof input !== 'object' || input === null) {
|
|
466
|
+
throw new Error('Invalid job: body must be an object');
|
|
467
|
+
}
|
|
468
|
+
const o = input;
|
|
469
|
+
// Validate the harness FIRST (before parseHarnessAuth, whose `pi` branch demands a proxy
|
|
470
|
+
// base URL): the inline path only ever runs a subscription CLI, so a Pi/absent harness is a
|
|
471
|
+
// clear inline-specific rejection rather than a confusing "proxyBaseUrl required".
|
|
472
|
+
if (o.harness !== 'claude-code' && o.harness !== 'codex') {
|
|
473
|
+
throw new Error("Invalid inline job: 'harness' must be 'claude-code' or 'codex'");
|
|
474
|
+
}
|
|
475
|
+
const auth = parseHarnessAuth(o);
|
|
476
|
+
const maxOutputTokens = posInt(o.maxOutputTokens);
|
|
477
|
+
return {
|
|
478
|
+
jobId: str(o.jobId, 'jobId'),
|
|
479
|
+
model: str(o.model, 'model'),
|
|
480
|
+
// The system prompt is optional (an empty role is valid); the user prompt is required.
|
|
481
|
+
systemPrompt: typeof o.systemPrompt === 'string' ? o.systemPrompt : '',
|
|
482
|
+
userPrompt: str(o.userPrompt, 'userPrompt'),
|
|
483
|
+
...auth,
|
|
484
|
+
...(maxOutputTokens !== undefined ? { maxOutputTokens } : {}),
|
|
485
|
+
};
|
|
486
|
+
}
|
|
458
487
|
/** Validate + narrow an untrusted body into an {@link AgentJob}, throwing on bad input. */
|
|
459
488
|
export function parseAgentJob(input) {
|
|
460
489
|
if (typeof input !== 'object' || input === null) {
|
package/dist/server.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { timingSafeEqual } from 'node:crypto';
|
|
2
2
|
import { createServer } from 'node:http';
|
|
3
|
-
import { parseAgentJob } from './job.js';
|
|
3
|
+
import { parseAgentJob, parseInlineJob } from './job.js';
|
|
4
4
|
import { handleAgent } from './agent.js';
|
|
5
|
+
import { handleInline } from './inline.js';
|
|
5
6
|
import { redactSecrets } from './git.js';
|
|
6
7
|
import { JobRegistry, loadRunnerLimits } from './runner.js';
|
|
7
8
|
import { log } from './logger.js';
|
|
@@ -67,6 +68,13 @@ const KINDS = {
|
|
|
67
68
|
repo: `${job.repo.owner}/${job.repo.name}`,
|
|
68
69
|
branch: job.branch,
|
|
69
70
|
})),
|
|
71
|
+
// The one-shot, no-checkout inline completion (requirements reviewer / brainstorm /
|
|
72
|
+
// task-estimator / inline document kinds) on a leased subscription credential — the
|
|
73
|
+
// container analogue of the local host-CLI inline runner. See inline.ts.
|
|
74
|
+
inline: defineKind(parseInlineJob, handleInline, (job) => ({
|
|
75
|
+
harness: job.harness,
|
|
76
|
+
model: job.model,
|
|
77
|
+
})),
|
|
70
78
|
};
|
|
71
79
|
async function readBody(req) {
|
|
72
80
|
const chunks = [];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cat-factory/executor-harness",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.37.2",
|
|
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/
|
|
30
|
-
"@cat-factory/
|
|
29
|
+
"@cat-factory/server": "0.101.0",
|
|
30
|
+
"@cat-factory/spend": "0.11.15"
|
|
31
31
|
},
|
|
32
32
|
"scripts": {
|
|
33
33
|
"build": "tsc -p tsconfig.json",
|
package/src/git.ts
CHANGED
|
@@ -61,14 +61,18 @@ const GIT_TIMEOUT_MS = Math.max(
|
|
|
61
61
|
// Emptying the helper list (`credential.helper=` with no value RESETS the multi-valued config,
|
|
62
62
|
// dropping the system/global/local helpers) removes GCM from the chain, so git falls back to
|
|
63
63
|
// the harness's own askpass helper — which returns the per-job PAT we already hold (see
|
|
64
|
-
// `authEnv`).
|
|
65
|
-
//
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
64
|
+
// `authEnv`). The token is never in argv; only this non-secret config is.
|
|
65
|
+
//
|
|
66
|
+
// DO NOT re-add `-c credential.interactive=false` here. It reads like harmless belt-and-braces
|
|
67
|
+
// but modern git (≥ 2.47, incl. the executor image + host git) HONORS `credential.interactive`
|
|
68
|
+
// and treats invoking GIT_ASKPASS as "interactive" — so with it set git SKIPS the askpass
|
|
69
|
+
// entirely and dies with "fatal: unable to get password from user", failing EVERY authenticated
|
|
70
|
+
// clone/push on both the native and container paths (it clones a public base repo fine — that
|
|
71
|
+
// needs no auth — then fails only at push, which is what makes it look intermittent). The GCM
|
|
72
|
+
// popup it was meant to belt-and-braces against is already fully handled by the emptied helper
|
|
73
|
+
// list above plus `GIT_TERMINAL_PROMPT=0` / `GCM_INTERACTIVE=never` in the env (see authEnv /
|
|
74
|
+
// nonInteractiveGitEnv). Exported so a unit test can pin that this arg never creeps back in.
|
|
75
|
+
export const NON_INTERACTIVE_CREDENTIAL_ARGS = ['-c', 'credential.helper=']
|
|
72
76
|
|
|
73
77
|
/**
|
|
74
78
|
* Env applied to git commands that DON'T carry {@link authEnv} (local ops like config/checkout/
|
package/src/inline.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { mkdtemp, rm } from 'node:fs/promises'
|
|
2
|
+
import { tmpdir } from 'node:os'
|
|
3
|
+
import { join } from 'node:path'
|
|
4
|
+
import { runSubscriptionHarness, type SubscriptionHarness } from './agent-runner.js'
|
|
5
|
+
import type { HarnessCallMetric } from './pi.js'
|
|
6
|
+
import type { InlineJob, InlineResult } from './job.js'
|
|
7
|
+
import type { RunOptions } from './runner.js'
|
|
8
|
+
|
|
9
|
+
// The `inline` job handler: one-shot LLM completion through a subscription harness CLI
|
|
10
|
+
// (Claude Code / Codex) with NO checkout. It is the container analogue of the local
|
|
11
|
+
// host-CLI inline runner (runtimes/local `harnessInline.ts`) — it exists so the inline
|
|
12
|
+
// LLM steps (requirements reviewer, brainstorm, task-estimator, inline document kinds) can
|
|
13
|
+
// run on a subscription model even when the host has no `claude`/`codex` binary (and in
|
|
14
|
+
// mothership mode without touching the host), at warm-pool latency. It reuses
|
|
15
|
+
// `runSubscriptionHarness`'s credential-env setup verbatim (the single site that turns a
|
|
16
|
+
// leased subscription token into `CLAUDE_CODE_OAUTH_TOKEN` / `ANTHROPIC_*` / a Codex
|
|
17
|
+
// `auth.json`), so the container and coding paths can never disagree on how a credential
|
|
18
|
+
// is injected.
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Map the harness CLI's terminal stop reason (lifted onto the last call metric) to the
|
|
22
|
+
* inline `finishReason` the reviewer keys off. Only Claude Code reports it (`max_tokens` on
|
|
23
|
+
* a `--output-format stream-json` result); Codex's thinner stream exposes none, so it reads
|
|
24
|
+
* as `stop` — the same one-shot limitation the host-CLI runner has.
|
|
25
|
+
*/
|
|
26
|
+
function deriveFinishReason(calls: HarnessCallMetric[] | undefined): 'stop' | 'length' {
|
|
27
|
+
const last = calls?.[calls.length - 1]
|
|
28
|
+
const reason = last?.finishReason?.toLowerCase() ?? ''
|
|
29
|
+
return reason === 'max_tokens' || reason === 'length' ? 'length' : 'stop'
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Run one inline completion in a throwaway temp cwd and return the reply text + lifted
|
|
34
|
+
* usage/telemetry. The CLI clones/pushes nothing — the empty cwd only gives it a working
|
|
35
|
+
* directory. The job's watchdog (inactivity + max-duration, see {@link JobRegistry}) bounds
|
|
36
|
+
* it through `opts.signal`; `opts.onActivity` keeps the inactivity timer alive while the CLI
|
|
37
|
+
* streams. The temp cwd is always removed.
|
|
38
|
+
*/
|
|
39
|
+
export async function handleInline(job: InlineJob, opts: RunOptions): Promise<InlineResult> {
|
|
40
|
+
opts.onPhase?.('agent')
|
|
41
|
+
const cwd = await mkdtemp(join(tmpdir(), 'cf-inline-'))
|
|
42
|
+
try {
|
|
43
|
+
const outcome = await runSubscriptionHarness(job.harness as SubscriptionHarness, {
|
|
44
|
+
cwd,
|
|
45
|
+
model: job.model,
|
|
46
|
+
systemPrompt: job.systemPrompt,
|
|
47
|
+
userPrompt: job.userPrompt,
|
|
48
|
+
...(job.subscriptionToken ? { subscriptionToken: job.subscriptionToken } : {}),
|
|
49
|
+
...(job.subscriptionBaseUrl ? { subscriptionBaseUrl: job.subscriptionBaseUrl } : {}),
|
|
50
|
+
...(job.ambientAuth ? { ambientAuth: true } : {}),
|
|
51
|
+
...(opts.signal ? { signal: opts.signal } : {}),
|
|
52
|
+
...(opts.onActivity ? { onActivity: opts.onActivity } : {}),
|
|
53
|
+
})
|
|
54
|
+
return {
|
|
55
|
+
text: outcome.summary,
|
|
56
|
+
finishReason: deriveFinishReason(outcome.callMetrics),
|
|
57
|
+
...(outcome.usage ? { usage: outcome.usage } : {}),
|
|
58
|
+
...(outcome.callMetrics ? { callMetrics: outcome.callMetrics } : {}),
|
|
59
|
+
}
|
|
60
|
+
} finally {
|
|
61
|
+
await rm(cwd, { recursive: true, force: true }).catch(() => {})
|
|
62
|
+
}
|
|
63
|
+
}
|
package/src/job.ts
CHANGED
|
@@ -923,6 +923,73 @@ function parseFrontendInfraSpec(o: Record<string, unknown>): FrontendInfraSpec {
|
|
|
923
923
|
}
|
|
924
924
|
}
|
|
925
925
|
|
|
926
|
+
// ---- Inline job (POST /jobs, kind=inline) --------------------------------
|
|
927
|
+
//
|
|
928
|
+
// A ONE-SHOT, no-checkout LLM completion run through a subscription harness CLI
|
|
929
|
+
// (Claude Code / Codex) on a leased subscription credential — the container analogue of
|
|
930
|
+
// the local host-CLI inline runner. It exists so a deployment that can't run the ambient
|
|
931
|
+
// CLI on the host (no `claude`/`codex` binary, or mothership mode) can still serve the
|
|
932
|
+
// inline LLM steps (requirements reviewer, brainstorm, task-estimator, inline document
|
|
933
|
+
// kinds) on a subscription model, at warm-pool latency. It clones NOTHING and pushes
|
|
934
|
+
// NOTHING: the CLI runs in a throwaway temp cwd and only the completion text + token usage
|
|
935
|
+
// come back. Auth is the SAME `HarnessAuthFields` the coding path uses (subscriptionToken +
|
|
936
|
+
// optional subscriptionBaseUrl, or ambientAuth), so the credential-env setup is shared.
|
|
937
|
+
|
|
938
|
+
/** The one-shot inline completion job. `harness` must be a subscription harness. */
|
|
939
|
+
export interface InlineJob extends HarnessAuthFields {
|
|
940
|
+
jobId: string
|
|
941
|
+
/** Real vendor model id, e.g. `claude-opus-4-8` / `gpt-5.5-codex`. */
|
|
942
|
+
model: string
|
|
943
|
+
/** Composed role + best-practice fragments (Claude: `--append-system-prompt`; Codex: prepended). */
|
|
944
|
+
systemPrompt: string
|
|
945
|
+
/** The concrete task/user prompt fed to the CLI over stdin. */
|
|
946
|
+
userPrompt: string
|
|
947
|
+
/** Advisory output cap, forwarded for parity; the one-shot CLIs don't all honour it. */
|
|
948
|
+
maxOutputTokens?: number
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
/** The inline completion result: the reply text plus lifted token usage / per-call telemetry. */
|
|
952
|
+
export interface InlineResult {
|
|
953
|
+
text: string
|
|
954
|
+
/** `length` when the model hit its output cap (the reviewer rejects a truncated doc). */
|
|
955
|
+
finishReason?: 'stop' | 'length'
|
|
956
|
+
usage?: { inputTokens: number; outputTokens: number }
|
|
957
|
+
/** Per-model-call telemetry lifted from the CLI stream (recorded into `llm_call_metrics`). */
|
|
958
|
+
callMetrics?: HarnessCallMetric[]
|
|
959
|
+
/** A structured failure marks a job-level failure even on a clean HTTP exit (see JobResultBase). */
|
|
960
|
+
error?: string
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
/**
|
|
964
|
+
* Validate + narrow an untrusted body into an {@link InlineJob}. The harness MUST be a
|
|
965
|
+
* subscription harness (`claude-code` / `codex`) — the inline path never runs Pi (that goes
|
|
966
|
+
* through the LLM proxy inline, not a container CLI). Reuses {@link parseHarnessAuth}, so a
|
|
967
|
+
* non-ambient job requires `subscriptionToken`.
|
|
968
|
+
*/
|
|
969
|
+
export function parseInlineJob(input: unknown): InlineJob {
|
|
970
|
+
if (typeof input !== 'object' || input === null) {
|
|
971
|
+
throw new Error('Invalid job: body must be an object')
|
|
972
|
+
}
|
|
973
|
+
const o = input as Record<string, unknown>
|
|
974
|
+
// Validate the harness FIRST (before parseHarnessAuth, whose `pi` branch demands a proxy
|
|
975
|
+
// base URL): the inline path only ever runs a subscription CLI, so a Pi/absent harness is a
|
|
976
|
+
// clear inline-specific rejection rather than a confusing "proxyBaseUrl required".
|
|
977
|
+
if (o.harness !== 'claude-code' && o.harness !== 'codex') {
|
|
978
|
+
throw new Error("Invalid inline job: 'harness' must be 'claude-code' or 'codex'")
|
|
979
|
+
}
|
|
980
|
+
const auth = parseHarnessAuth(o)
|
|
981
|
+
const maxOutputTokens = posInt(o.maxOutputTokens)
|
|
982
|
+
return {
|
|
983
|
+
jobId: str(o.jobId, 'jobId'),
|
|
984
|
+
model: str(o.model, 'model'),
|
|
985
|
+
// The system prompt is optional (an empty role is valid); the user prompt is required.
|
|
986
|
+
systemPrompt: typeof o.systemPrompt === 'string' ? o.systemPrompt : '',
|
|
987
|
+
userPrompt: str(o.userPrompt, 'userPrompt'),
|
|
988
|
+
...auth,
|
|
989
|
+
...(maxOutputTokens !== undefined ? { maxOutputTokens } : {}),
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
|
|
926
993
|
/** Validate + narrow an untrusted body into an {@link AgentJob}, throwing on bad input. */
|
|
927
994
|
export function parseAgentJob(input: unknown): AgentJob {
|
|
928
995
|
if (typeof input !== 'object' || input === null) {
|
package/src/server.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { timingSafeEqual } from 'node:crypto'
|
|
2
2
|
import { createServer, type IncomingMessage, type ServerResponse } from 'node:http'
|
|
3
|
-
import { parseAgentJob } from './job.js'
|
|
3
|
+
import { parseAgentJob, parseInlineJob } from './job.js'
|
|
4
4
|
import { handleAgent } from './agent.js'
|
|
5
|
+
import { handleInline } from './inline.js'
|
|
5
6
|
import { redactSecrets } from './git.js'
|
|
6
7
|
import { JobRegistry, loadRunnerLimits, type JobResultBase, type RunOptions } from './runner.js'
|
|
7
8
|
import { log } from './logger.js'
|
|
@@ -84,6 +85,13 @@ const KINDS: Record<string, KindEntry> = {
|
|
|
84
85
|
repo: `${job.repo.owner}/${job.repo.name}`,
|
|
85
86
|
branch: job.branch,
|
|
86
87
|
})),
|
|
88
|
+
// The one-shot, no-checkout inline completion (requirements reviewer / brainstorm /
|
|
89
|
+
// task-estimator / inline document kinds) on a leased subscription credential — the
|
|
90
|
+
// container analogue of the local host-CLI inline runner. See inline.ts.
|
|
91
|
+
inline: defineKind(parseInlineJob, handleInline, (job) => ({
|
|
92
|
+
harness: job.harness,
|
|
93
|
+
model: job.model,
|
|
94
|
+
})),
|
|
87
95
|
}
|
|
88
96
|
|
|
89
97
|
async function readBody(req: IncomingMessage): Promise<string> {
|