@evident-ai/runner-cdk 0.1.1-dev.04ef8b1 → 0.1.1-dev.04f2155
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 +21 -12
- package/dist/evident-scale-to-zero-construct.js +3 -4
- package/dist/microvm/construct.d.ts +16 -2
- package/dist/microvm/construct.js +12 -1
- package/dist/microvm-image-context/Dockerfile +1 -1
- package/dist/microvm-image-context/hooks/common.sh +110 -6
- package/dist/microvm-image-context/hooks/run +9 -3
- package/package.json +6 -5
package/README.md
CHANGED
|
@@ -9,9 +9,9 @@ Reusable AWS CDK constructs for running an [Evident](https://evident.run) agent
|
|
|
9
9
|
`desiredCount 0`, and an HMAC-authenticated waker Lambda scales it back up on the next
|
|
10
10
|
message. You pay for the time the agent is actually working.
|
|
11
11
|
- **`EvidentMicrovmConstruct`** — a per-session AWS Lambda MicroVM that boots on demand
|
|
12
|
-
and suspends between messages. Installing this package is all you
|
|
13
|
-
context ships inside it (see [The MicroVM image](#the-microvm-image)
|
|
14
|
-
no checkout of `sroze/evident` involved. See
|
|
12
|
+
and suspends between messages. Installing the `@dev` tag of this package is all you
|
|
13
|
+
need: the image build context ships inside it (see [The MicroVM image](#the-microvm-image)
|
|
14
|
+
below), so there is no checkout of `sroze/evident` involved. See
|
|
15
15
|
[the AWS runner doc](https://evident.run/docs/aws-runner) for the full picture of both
|
|
16
16
|
strategies.
|
|
17
17
|
|
|
@@ -34,8 +34,9 @@ cluster can host several agents.
|
|
|
34
34
|
## Getting started
|
|
35
35
|
|
|
36
36
|
For a full end-to-end walkthrough — shared infra, secrets without SOPS/KMS, the image,
|
|
37
|
-
the two grants, deploy, and wiring the wake webhook — see
|
|
38
|
-
|
|
37
|
+
the two grants, deploy, and wiring the wake webhook — see `GETTING-STARTED.md` in this
|
|
38
|
+
package's directory (not published to npm; read it from a checkout of the repo). The
|
|
39
|
+
snippet below is the short version.
|
|
39
40
|
|
|
40
41
|
```ts
|
|
41
42
|
import { EvidentScaleToZeroConstruct } from "@evident-ai/runner-cdk";
|
|
@@ -126,6 +127,7 @@ the per-phase hook scripts and the bundled hook server, published inside the tar
|
|
|
126
127
|
the agent's workspace. `stageMicrovmImageContext()` puts the two together:
|
|
127
128
|
|
|
128
129
|
```ts
|
|
130
|
+
import path from 'node:path';
|
|
129
131
|
import {
|
|
130
132
|
EvidentMicrovmConstruct,
|
|
131
133
|
HOOKS_PORT,
|
|
@@ -149,6 +151,8 @@ new EvidentMicrovmConstruct(this, 'Runner', {
|
|
|
149
151
|
baseImageArn,
|
|
150
152
|
baseImageVersion,
|
|
151
153
|
doorbellSecret,
|
|
154
|
+
runnerSecret,
|
|
155
|
+
runnerOpencodeConfigPath: 'opencode.runner.jsonc',
|
|
152
156
|
});
|
|
153
157
|
```
|
|
154
158
|
|
|
@@ -156,14 +160,19 @@ Your repo does not have to be a pnpm workspace. If it commits a `pnpm-lock.yaml`
|
|
|
156
160
|
pre-installs dependencies at build time (a faster first boot); if not, that step is skipped
|
|
157
161
|
and the agent installs on first use.
|
|
158
162
|
|
|
163
|
+
| MicroVM prop | Omitted behavior |
|
|
164
|
+
| --- | --- |
|
|
165
|
+
| `runnerSecret` | No GitHub or MCP credentials are exported at `/run`. |
|
|
166
|
+
| `runnerOpencodeConfigPath` | OpenCode uses the baked project configuration. Relative paths resolve from the workspace; absolute paths resolve in the image. |
|
|
167
|
+
| `gitUserName` / `gitUserEmail` | The hook uses the Evident bot defaults for git identity. |
|
|
168
|
+
|
|
159
169
|
## Status / limitations
|
|
160
170
|
|
|
161
|
-
- **Published to npm** as `@evident-ai/runner-cdk` (MIT) —
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
dependency for dogfooding.
|
|
171
|
+
- **Published to npm** as `@evident-ai/runner-cdk` (MIT). Install the `@dev` tag —
|
|
172
|
+
`npm install @evident-ai/runner-cdk@dev` (or `pnpm add`/`yarn add`); no checkout of
|
|
173
|
+
this repo is required. It builds to a self-contained `dist/` (`pnpm --filter
|
|
174
|
+
@evident-ai/runner-cdk build`) with no `workspace:`/`@evident/*` runtime dependency.
|
|
175
|
+
This repo's own consumer (`infrastructure/evident-runner`) still consumes it as a
|
|
176
|
+
`workspace:*` dependency for dogfooding.
|
|
168
177
|
- **AWS/ECS-specific.** Non-AWS clouds are an open question on the epic, not a supported
|
|
169
178
|
path.
|
|
@@ -115,10 +115,9 @@ class EvidentScaleToZeroConstruct extends constructs_1.Construct {
|
|
|
115
115
|
// window (protected/active sessions are never touched). Tightened from 7d
|
|
116
116
|
// to 24h per #537: at 7d, opencode.db was observed growing ~11x (77 MB ->
|
|
117
117
|
// 849 MB) in ~24h, so 7d was far too permissive at the observed growth
|
|
118
|
-
// rate. 24h is
|
|
119
|
-
//
|
|
120
|
-
//
|
|
121
|
-
// interval and never touches a protected/active session.
|
|
118
|
+
// rate. 24h is the tighter mitigation that came out of that (see also the
|
|
119
|
+
// OPENCODE_VERSION pin above). Sweep runs on the default 1h interval and
|
|
120
|
+
// never touches a protected/active session.
|
|
122
121
|
EVIDENT_SESSION_CLEANUP_MAX_AGE: '24h',
|
|
123
122
|
CLUSTER: cluster.clusterName,
|
|
124
123
|
SERVICE: serviceName,
|
|
@@ -12,7 +12,7 @@ export interface EvidentMicrovmConstructProps {
|
|
|
12
12
|
readonly imageSource: string;
|
|
13
13
|
/**
|
|
14
14
|
* ARN of the Lambda-managed base MicroVM image, from
|
|
15
|
-
* `aws lambda list-managed-microvm-images`. A string, not a `CfnParameter`
|
|
15
|
+
* `aws lambda-microvms list-managed-microvm-images`. A string, not a `CfnParameter`
|
|
16
16
|
* built here: the caller creates the parameter at ITS OWN stack scope
|
|
17
17
|
* (preserving its logical id) and passes its resolved value down — a
|
|
18
18
|
* construct must not mint parameters into a consumer's stack.
|
|
@@ -20,7 +20,7 @@ export interface EvidentMicrovmConstructProps {
|
|
|
20
20
|
readonly baseImageArn: string;
|
|
21
21
|
/**
|
|
22
22
|
* Version of the Lambda-managed base MicroVM image, from
|
|
23
|
-
* `aws lambda list-managed-microvm-images`. See `baseImageArn` for why this
|
|
23
|
+
* `aws lambda-microvms list-managed-microvm-images`. See `baseImageArn` for why this
|
|
24
24
|
* is a plain string.
|
|
25
25
|
*/
|
|
26
26
|
readonly baseImageVersion: string;
|
|
@@ -41,6 +41,20 @@ export interface EvidentMicrovmConstructProps {
|
|
|
41
41
|
* in its own scope, and passes the result in either case.
|
|
42
42
|
*/
|
|
43
43
|
readonly doorbellSecret: secretsmanager.ISecret;
|
|
44
|
+
/**
|
|
45
|
+
* Optional runner secret whose JSON values are exported by `/run`. Omitting it
|
|
46
|
+
* leaves GitHub and MCP credentials unavailable to a generic consumer.
|
|
47
|
+
*/
|
|
48
|
+
readonly runnerSecret?: secretsmanager.ISecret;
|
|
49
|
+
/**
|
|
50
|
+
* Optional runner OpenCode overlay. Absolute paths are image paths; relative
|
|
51
|
+
* paths are resolved from the baked workspace by the `/run` hook.
|
|
52
|
+
*/
|
|
53
|
+
readonly runnerOpencodeConfigPath?: string;
|
|
54
|
+
/** Optional git identity the `/run` hook configures for agent commits. */
|
|
55
|
+
readonly gitUserName?: string;
|
|
56
|
+
/** Optional git email the `/run` hook configures for agent commits. */
|
|
57
|
+
readonly gitUserEmail?: string;
|
|
44
58
|
/**
|
|
45
59
|
* TCP port the image's hook server listens on, baked into both the image
|
|
46
60
|
* (`HOOKS_PORT` env var) and the `MicrovmImage`'s `hooks.port` — a mismatch
|
|
@@ -81,7 +81,9 @@ class EvidentMicrovmConstruct extends constructs_1.Construct {
|
|
|
81
81
|
// ANDed with the age condition and counts newer noncurrent versions of
|
|
82
82
|
// the SAME key, but litestream's LTX keys are txid-unique and churn by
|
|
83
83
|
// DELETE, so a churned key has exactly one noncurrent version and zero
|
|
84
|
-
// newer ones.
|
|
84
|
+
// newer ones. `expiredObjectDeleteMarker` reaps the delete marker itself once
|
|
85
|
+
// its noncurrent version is gone — without it, the version index grows
|
|
86
|
+
// unboundedly, one marker per churned LTX key.
|
|
85
87
|
const durableState = new s3.Bucket(this, 'DurableState', {
|
|
86
88
|
encryption: s3.BucketEncryption.S3_MANAGED,
|
|
87
89
|
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
|
|
@@ -98,6 +100,7 @@ class EvidentMicrovmConstruct extends constructs_1.Construct {
|
|
|
98
100
|
{
|
|
99
101
|
id: 'ExpireNoncurrentVersions',
|
|
100
102
|
noncurrentVersionExpiration: cdk.Duration.days(7),
|
|
103
|
+
expiredObjectDeleteMarker: true,
|
|
101
104
|
},
|
|
102
105
|
],
|
|
103
106
|
});
|
|
@@ -118,6 +121,12 @@ class EvidentMicrovmConstruct extends constructs_1.Construct {
|
|
|
118
121
|
// already bakes in for its own runners (evident-scale-to-zero-construct.ts:191).
|
|
119
122
|
// Safe to bake: identical for every VM from this image version.
|
|
120
123
|
EVIDENT_SESSION_CLEANUP_MAX_AGE: '24h',
|
|
124
|
+
...(props.runnerOpencodeConfigPath
|
|
125
|
+
? { RUNNER_OPENCODE_CONFIG: props.runnerOpencodeConfigPath }
|
|
126
|
+
: {}),
|
|
127
|
+
...(props.runnerSecret ? { RUNNER_SECRET_ARN: props.runnerSecret.secretArn } : {}),
|
|
128
|
+
...(props.gitUserName ? { GIT_USER_NAME: props.gitUserName } : {}),
|
|
129
|
+
...(props.gitUserEmail ? { GIT_USER_EMAIL: props.gitUserEmail } : {}),
|
|
121
130
|
};
|
|
122
131
|
// ...and the hooks configuration every shape's image is built with.
|
|
123
132
|
const imageHooks = {
|
|
@@ -176,6 +185,8 @@ class EvidentMicrovmConstruct extends constructs_1.Construct {
|
|
|
176
185
|
// customer account, and what Phase 4 (#263) has to tighten when this goes
|
|
177
186
|
// multi-tenant.
|
|
178
187
|
durableState.grantReadWrite(defaultImage.executionRole);
|
|
188
|
+
// The image receives only an ARN; `/run` reads the secret value at runtime.
|
|
189
|
+
props.runnerSecret?.grantRead(defaultImage.executionRole);
|
|
179
190
|
// The shape catalogue the controller resolves a doorbell's `shape` field
|
|
180
191
|
// against, and advertises over the describe channel (#723). One JSON
|
|
181
192
|
// array, built with `Stack.toJsonString` — not `JSON.stringify` — because
|
|
@@ -37,7 +37,7 @@ RUN corepack enable && corepack prepare pnpm@9.15.0 --activate
|
|
|
37
37
|
# binary assertion further down fails the build if any required binary is gone.
|
|
38
38
|
RUN apt-get update \
|
|
39
39
|
&& apt-get install -y --no-install-recommends \
|
|
40
|
-
ca-certificates curl git jq less procps ripgrep \
|
|
40
|
+
ca-certificates curl git jq less procps psmisc ripgrep \
|
|
41
41
|
postgresql postgresql-contrib direnv \
|
|
42
42
|
gnupg unzip \
|
|
43
43
|
# --- GitHub CLI (`gh`) from the official apt repo ---
|
|
@@ -211,13 +211,13 @@ load_state_config() {
|
|
|
211
211
|
# `model-auth-ready` therefore stays a boot-time diagnostic: its only output is
|
|
212
212
|
# a log line, since the runtime forwards no hook stderr to the doorbell caller.
|
|
213
213
|
|
|
214
|
-
# How long restore_credentials' shared step budget is, covering
|
|
215
|
-
# its synchroniser calls (restore claude, restore opencode,
|
|
216
|
-
# as one window rather than a timeout apiece — a fixed per-call cap at the same
|
|
214
|
+
# How long restore_credentials' shared step budget is, covering the runner-secret
|
|
215
|
+
# fetch plus its three synchroniser calls (restore claude, restore opencode,
|
|
216
|
+
# model-auth-ready) as one window rather than a timeout apiece — a fixed per-call cap at the same
|
|
217
217
|
# total would false-fire on the ordinary case of one slow call (a slow S3 GET),
|
|
218
218
|
# which is precisely the boot this exists to keep healthy. From #930's 3-boot
|
|
219
|
-
# sample: the step measured ~3-5s total (~1.2s per call, three
|
|
220
|
-
# starts of a 1.8 MB bundle) — a WEAK estimate this file's own
|
|
219
|
+
# sample: the step measured ~3-5s total (~1.2s per synchroniser call, three
|
|
220
|
+
# node cold starts of a 1.8 MB bundle) — a WEAK estimate this file's own
|
|
221
221
|
# SYNCHRONISER-TIMING lines are what will sharpen for real. Too tight and a
|
|
222
222
|
# routine slow call boots this VM with no model credentials until an operator
|
|
223
223
|
# reconnects it; too loose and a hung call burns more of the hook's own
|
|
@@ -229,6 +229,7 @@ CREDENTIAL_RESTORE_DEADLINE_SECONDS="${EVIDENT_CREDENTIAL_RESTORE_DEADLINE_SECON
|
|
|
229
229
|
# and a call that ignored it would be unbounded again). Hard ceiling on the
|
|
230
230
|
# step: DEADLINE + this = 10s, once — not per call.
|
|
231
231
|
CREDENTIAL_RESTORE_KILL_GRACE_SECONDS=2
|
|
232
|
+
GITHUB_PROBE_DEADLINE_SECONDS="${EVIDENT_GITHUB_PROBE_DEADLINE_SECONDS:-10}"
|
|
232
233
|
|
|
233
234
|
# What is left of the shared step budget, in whole seconds, `step_started_s`
|
|
234
235
|
# seconds after it began. `SECONDS` (a bash builtin with no failure mode,
|
|
@@ -268,12 +269,66 @@ bounded_restore_call() {
|
|
|
268
269
|
return 0
|
|
269
270
|
}
|
|
270
271
|
|
|
272
|
+
fetch_runner_secret() {
|
|
273
|
+
local step_started_s="$1" remaining rc=0 payload value stderr_file started_ms populated=0 missing=0
|
|
274
|
+
if [ -z "${RUNNER_SECRET_ARN:-}" ]; then
|
|
275
|
+
log "runner secret is not configured; continuing without GitHub and MCP credentials"
|
|
276
|
+
return 0
|
|
277
|
+
fi
|
|
278
|
+
remaining="$(remaining_credential_budget "${step_started_s}")"
|
|
279
|
+
if [ "${remaining}" -lt 1 ]; then
|
|
280
|
+
warn "CREDENTIAL-RESTORE-SKIPPED: runner-secret skipped; the ${CREDENTIAL_RESTORE_DEADLINE_SECONDS}s credential-restore budget is exhausted"
|
|
281
|
+
return 0
|
|
282
|
+
fi
|
|
283
|
+
if ! stderr_file="$(mktemp /dev/shm/runner-secret-stderr.XXXXXX)"; then
|
|
284
|
+
warn "RUNNER-SECRET-STDERR-UNAVAILABLE: could not allocate diagnostic storage; continuing without runner credentials"
|
|
285
|
+
return 0
|
|
286
|
+
fi
|
|
287
|
+
started_ms="$(now_ms)"
|
|
288
|
+
payload="$(timeout -k "${CREDENTIAL_RESTORE_KILL_GRACE_SECONDS}" "${remaining}" aws secretsmanager get-secret-value --secret-id "${RUNNER_SECRET_ARN}" --query SecretString --output text 2>"${stderr_file}")" || rc=$?
|
|
289
|
+
log_elapsed_since runner-secret-fetch "${started_ms}" "${rc}"
|
|
290
|
+
if [ "${rc}" -eq 124 ] || [ "${rc}" -eq 137 ]; then
|
|
291
|
+
warn "CREDENTIAL-RESTORE-TIMEOUT: runner-secret did not finish within its ${remaining}s share of the ${CREDENTIAL_RESTORE_DEADLINE_SECONDS}s credential-restore deadline; continuing without it"
|
|
292
|
+
rm -f "${stderr_file}"
|
|
293
|
+
return 0
|
|
294
|
+
fi
|
|
295
|
+
if [ "${rc}" -ne 0 ]; then
|
|
296
|
+
warn "RUNNER-SECRET-UNREADABLE: $(<"${stderr_file}")"
|
|
297
|
+
rm -f "${stderr_file}"
|
|
298
|
+
return 0
|
|
299
|
+
fi
|
|
300
|
+
rm -f "${stderr_file}"
|
|
301
|
+
if ! jq -e 'type == "object"' >/dev/null 2>&1 <<<"${payload}"; then
|
|
302
|
+
warn "RUNNER-SECRET-UNPARSEABLE: secret value is not a JSON object"
|
|
303
|
+
return 0
|
|
304
|
+
fi
|
|
305
|
+
local -a populated_keys=()
|
|
306
|
+
for key in GH_TOKEN BRAVE_API_KEY CLOUDFLARE_API_TOKEN NEON_API_KEY; do
|
|
307
|
+
value="$(jq -r --arg k "${key}" '.[$k] // empty' <<<"${payload}")"
|
|
308
|
+
if [ -n "${value}" ]; then
|
|
309
|
+
export "${key}=${value}"
|
|
310
|
+
populated=$((populated + 1))
|
|
311
|
+
populated_keys+=("${key}")
|
|
312
|
+
else
|
|
313
|
+
missing=$((missing + 1))
|
|
314
|
+
fi
|
|
315
|
+
done
|
|
316
|
+
if [ "${populated}" -eq 0 ]; then
|
|
317
|
+
warn "RUNNER-SECRET-UNPOPULATED: populate the runner secret as documented in infrastructure/evident-microvm/README.md"
|
|
318
|
+
else
|
|
319
|
+
log "RUNNER-SECRET-OK: populated ${populated_keys[*]}; ${missing} allow-listed keys empty or absent"
|
|
320
|
+
fi
|
|
321
|
+
return 0
|
|
322
|
+
}
|
|
323
|
+
|
|
271
324
|
restore_credentials() {
|
|
272
325
|
local step_started_ms step_started_s
|
|
273
326
|
step_started_ms="$(now_ms)"
|
|
274
327
|
step_started_s="${SECONDS}"
|
|
275
328
|
|
|
276
329
|
load_state_config || return 1
|
|
330
|
+
# This shares the existing bounded window so /run's worst-case duration does not grow.
|
|
331
|
+
fetch_runner_secret "${step_started_s}"
|
|
277
332
|
bounded_restore_call restore-claude "${step_started_s}" restore claude || return 1
|
|
278
333
|
bounded_restore_call restore-opencode "${step_started_s}" restore opencode || return 1
|
|
279
334
|
|
|
@@ -313,7 +368,7 @@ infrastructure/evident-microvm/README.md."
|
|
|
313
368
|
fi
|
|
314
369
|
# The step total, in the SAME greppable shape as the per-call lines above, so
|
|
315
370
|
# one query answers both "what does credential restore cost?" and "which of
|
|
316
|
-
# its
|
|
371
|
+
# its four operations cost it". Bounded now by CREDENTIAL_RESTORE_DEADLINE_SECONDS
|
|
317
372
|
# + CREDENTIAL_RESTORE_KILL_GRACE_SECONDS as one shared window (see the
|
|
318
373
|
# comment above that constant) rather than per call. `rc=0` is deliberate
|
|
319
374
|
# here, not an absence of failure modes: a timed-out call already warned by
|
|
@@ -323,6 +378,55 @@ infrastructure/evident-microvm/README.md."
|
|
|
323
378
|
return 0
|
|
324
379
|
}
|
|
325
380
|
|
|
381
|
+
apply_runner_opencode_config() {
|
|
382
|
+
if [ -z "${RUNNER_OPENCODE_CONFIG:-}" ]; then
|
|
383
|
+
log "runner OpenCode config is not configured; using the baked project config"
|
|
384
|
+
return 0
|
|
385
|
+
fi
|
|
386
|
+
local source="${RUNNER_OPENCODE_CONFIG}" target="opencode.json"
|
|
387
|
+
[[ "${source}" = /* ]] || source="${WORKSPACE}/${source}"
|
|
388
|
+
[ -f "${WORKSPACE}/opencode.jsonc" ] && target="opencode.jsonc"
|
|
389
|
+
if [ ! -f "${source}" ]; then
|
|
390
|
+
error "RUNNER-OPENCODE-CONFIG-MISSING: ${source} is not a file; headless turns will wedge on the first external-directory permission prompt (#563)"
|
|
391
|
+
return 0
|
|
392
|
+
fi
|
|
393
|
+
cp "${source}" "${WORKSPACE}/${target}"
|
|
394
|
+
git -C "${WORKSPACE}" update-index --skip-worktree "${target}" 2>/dev/null \
|
|
395
|
+
|| warn "could not mark ${target} skip-worktree; it may show as a local change"
|
|
396
|
+
log "Applied runner OpenCode config ${source} to ${WORKSPACE}/${target}"
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
configure_github_access() {
|
|
400
|
+
if [ -z "${GH_TOKEN:-}" ]; then
|
|
401
|
+
warn "GITHUB-CREDENTIALS-MISSING: GH_TOKEN is unavailable; see RUNNER-SECRET-* above"
|
|
402
|
+
return 0
|
|
403
|
+
fi
|
|
404
|
+
export GIT_CONFIG_GLOBAL=/tmp/gitconfig
|
|
405
|
+
if ! : >"${GIT_CONFIG_GLOBAL}" ||
|
|
406
|
+
! git config --global user.name "${GIT_USER_NAME:-evident-bot}" ||
|
|
407
|
+
! git config --global user.email "${GIT_USER_EMAIL:-evident-bot@users.noreply.github.com}" ||
|
|
408
|
+
! git config --global init.defaultBranch main ||
|
|
409
|
+
! printf '%s\n' '#!/usr/bin/env bash' '[ "$1" = get ] || exit 0' 'echo username=x-access-token' 'echo "password=${GH_TOKEN}"' >/tmp/git-credential-helper.sh ||
|
|
410
|
+
! chmod 0700 /tmp/git-credential-helper.sh ||
|
|
411
|
+
! git config --global credential."https://github.com".helper /tmp/git-credential-helper.sh; then
|
|
412
|
+
warn "GITHUB-SETUP-FAILED: could not configure local git credentials; continuing without GitHub access"
|
|
413
|
+
return 0
|
|
414
|
+
fi
|
|
415
|
+
(
|
|
416
|
+
local output rc=0 login repo_url repo
|
|
417
|
+
output="$(timeout -k 1 "${GITHUB_PROBE_DEADLINE_SECONDS}" gh api user --jq .login 2>&1)" || rc=$?
|
|
418
|
+
if [ "${rc}" -eq 124 ] || [ "${rc}" -eq 137 ]; then warn "GITHUB-PROBE-TIMEOUT: auth probe exceeded ${GITHUB_PROBE_DEADLINE_SECONDS}s"; return; fi
|
|
419
|
+
if [ "${rc}" -ne 0 ]; then warn "GITHUB-AUTH-REJECTED: ${output}"; return; fi
|
|
420
|
+
log "GITHUB-AUTH-OK: ${output}"
|
|
421
|
+
repo_url="$(git -C "${WORKSPACE}" remote get-url origin 2>/dev/null || true)"
|
|
422
|
+
repo="$(printf '%s' "${repo_url}" | sed -E 's#(https://github.com/|git@github.com:)##; s#\.git$##')"
|
|
423
|
+
[ -n "${repo}" ] || return
|
|
424
|
+
output="$(timeout -k 1 "${GITHUB_PROBE_DEADLINE_SECONDS}" gh api "repos/${repo}" --jq .full_name 2>&1)" || rc=$?
|
|
425
|
+
if [ "${rc}" -eq 124 ] || [ "${rc}" -eq 137 ]; then warn "GITHUB-PROBE-TIMEOUT: repository probe for ${repo} exceeded ${GITHUB_PROBE_DEADLINE_SECONDS}s"; return; fi
|
|
426
|
+
[ "${rc}" -eq 0 ] || warn "GITHUB-REPO-INACCESSIBLE: ${repo}: ${output}"
|
|
427
|
+
) &
|
|
428
|
+
}
|
|
429
|
+
|
|
326
430
|
# Best-effort by design: /suspend must still drop the tunnel and /terminate must
|
|
327
431
|
# still clean up, so a flush that cannot happen is loud but never fatal.
|
|
328
432
|
sync_credentials() {
|
|
@@ -79,7 +79,13 @@ check_runner_key "${runner_key}" "${api_url}" || exit 1
|
|
|
79
79
|
restore_session_db
|
|
80
80
|
log "session DB restore done ${SECONDS}s into the hook"
|
|
81
81
|
|
|
82
|
-
# 7 —
|
|
82
|
+
# 7 — apply the overlay before OpenCode resolves its project configuration.
|
|
83
|
+
apply_runner_opencode_config
|
|
84
|
+
|
|
85
|
+
# 8 — configure git after credentials are restored and before agent shells start.
|
|
86
|
+
configure_github_access
|
|
87
|
+
|
|
88
|
+
# 9 — opencode. Started here, not at build time: a warm process in the shared
|
|
83
89
|
# snapshot would carry its installation id and database into every VM. Not
|
|
84
90
|
# waited on: a slow opencode boot is not a reason to fail /run (the tunnel CLI
|
|
85
91
|
# auto-starts opencode when it finds none healthy,
|
|
@@ -87,14 +93,14 @@ log "session DB restore done ${SECONDS}s into the hook"
|
|
|
87
93
|
# reclaims a runner that never comes online).
|
|
88
94
|
start_opencode
|
|
89
95
|
|
|
90
|
-
#
|
|
96
|
+
# 10 — begin replicating the session DB (#812 WI-3), now that opencode has
|
|
91
97
|
# opened it and before any work can arrive over the tunnel. Bare, like
|
|
92
98
|
# restore_session_db above: start_litestream never returns non-zero (every
|
|
93
99
|
# guard inside it is its own `return 0`), so there is nothing here for
|
|
94
100
|
# `set -e` to abort on.
|
|
95
101
|
start_litestream
|
|
96
102
|
|
|
97
|
-
#
|
|
103
|
+
# 11 — the first per-VM identity on the wire. The subshell's umask makes the file
|
|
98
104
|
# unreadable to anyone else from the moment it exists, before the key is in it.
|
|
99
105
|
(
|
|
100
106
|
umask 077
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evident-ai/runner-cdk",
|
|
3
|
-
"version": "0.1.1-dev.
|
|
3
|
+
"version": "0.1.1-dev.04f2155",
|
|
4
4
|
"description": "Reusable CDK constructs for an Evident agent runner: a single scale-to-zero Fargate runner (task + service + per-agent self-stop role + waker Lambda), or a per-session AWS Lambda MicroVM that boots on demand and suspends between messages. Instantiate once per agent from your own stack.",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -15,13 +15,14 @@
|
|
|
15
15
|
],
|
|
16
16
|
"scripts": {
|
|
17
17
|
"build": "pnpm run build-bundled-deps && ts-node scripts/build.ts",
|
|
18
|
-
"//build-bundled-deps": "scripts/build.ts esbuild-bundles src/microvm/image/hook-server.ts, which imports @evident-ai/lambda-microvm-runtime by its built `main`. Chained into `build` rather than left to turbo's `^build` because publish-runner-cdk.yaml
|
|
19
|
-
"build-bundled-deps": "pnpm --filter @evident-ai/lambda-microvm-runtime build",
|
|
18
|
+
"//build-bundled-deps": "scripts/build.ts esbuild-bundles src/microvm/image/hook-server.ts, which imports @evident-ai/lambda-microvm-runtime by its built `main`. @evident-ai/lambda-microvm-cdk is a workspace:* devDependency whose types resolve through its own dist/, so tsc --project tsconfig.build.json cannot compile src/microvm/{shapes,construct}.ts without it. Chained into `build` rather than left to turbo's `^build` because publish-runner-cdk.yaml and infrastructure/evident-runner's build-runner-cdk-dep invoke `pnpm --filter @evident-ai/runner-cdk build` directly, which bypasses turbo entirely.",
|
|
19
|
+
"build-bundled-deps": "pnpm --filter @evident-ai/lambda-microvm-cdk build && pnpm --filter @evident-ai/lambda-microvm-runtime build",
|
|
20
20
|
"typecheck": "tsc --noEmit",
|
|
21
21
|
"test": "node --test --require ts-node/register 'src/**/*.test.ts'",
|
|
22
22
|
"format": "prettier --write 'src/**/*.ts'",
|
|
23
23
|
"lint": "eslint 'src/**/*.ts' --max-warnings=0"
|
|
24
24
|
},
|
|
25
|
+
"//peerDependencies": "@evident-ai/lambda-microvm-cdk is a registry range here, never workspace:*: publish-runner-cdk.yaml ships this package with `npm publish`, not `pnpm publish`, so pnpm's workspace-protocol substitution never runs and a workspace: range would reach npm literally, breaking every external install. Enforced by infrastructure/evident-runner/src/runner-cdk-published-surface.test.ts. The matching devDependency is workspace:* so this monorepo's own build compiles against packages/lambda-microvm-cdk's source.",
|
|
25
26
|
"peerDependencies": {
|
|
26
27
|
"@evident-ai/lambda-microvm-cdk": "^0.1.1",
|
|
27
28
|
"aws-cdk-lib": "^2.240.0",
|
|
@@ -31,7 +32,7 @@
|
|
|
31
32
|
"@aws-sdk/client-ecs": "^3.682.0",
|
|
32
33
|
"@aws-sdk/client-lambda-microvms": "^3.1095.0",
|
|
33
34
|
"@aws-sdk/client-secrets-manager": "^3.682.0",
|
|
34
|
-
"@evident-ai/lambda-microvm-cdk": "
|
|
35
|
+
"@evident-ai/lambda-microvm-cdk": "workspace:*",
|
|
35
36
|
"@evident-ai/lambda-microvm-runtime": "workspace:*",
|
|
36
37
|
"@evident/webhook-signature": "workspace:*",
|
|
37
38
|
"@types/node": "^22",
|
|
@@ -51,6 +52,6 @@
|
|
|
51
52
|
"directory": "packages/runner-cdk"
|
|
52
53
|
},
|
|
53
54
|
"homepage": "https://evident.run",
|
|
54
|
-
"author": "Evident <
|
|
55
|
+
"author": "Evident <hello@evident.run>",
|
|
55
56
|
"license": "MIT"
|
|
56
57
|
}
|