Update stuff
This commit is contained in:
@@ -53,6 +53,8 @@ jobs:
|
|||||||
printf '%s\n' "$DOTENV_PROD" > "$env_file"
|
printf '%s\n' "$DOTENV_PROD" > "$env_file"
|
||||||
CI_ENV_FILE="$env_file" ./scripts/build-next-app production
|
CI_ENV_FILE="$env_file" ./scripts/build-next-app production
|
||||||
- name: Build agent images
|
- name: Build agent images
|
||||||
|
env:
|
||||||
|
SPOON_BUILD_SHA: ${{ gitea.sha }}
|
||||||
run: SPOON_AGENT_CONTAINER_RUNTIME=docker ./scripts/build-agent-images
|
run: SPOON_AGENT_CONTAINER_RUNTIME=docker ./scripts/build-agent-images
|
||||||
- name: Tag and push images
|
- name: Tag and push images
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -61,6 +61,10 @@
|
|||||||
- Host-run worker dev uses `scripts/dev-agent-worker` after Infisical env
|
- Host-run worker dev uses `scripts/dev-agent-worker` after Infisical env
|
||||||
loading. It prefers Podman, sets `SPOON_AGENT_CONTAINER_ACCESS=host_port`,
|
loading. It prefers Podman, sets `SPOON_AGENT_CONTAINER_ACCESS=host_port`,
|
||||||
and expects `spoon-agent-job:latest` to exist locally.
|
and expects `spoon-agent-job:latest` to exist locally.
|
||||||
|
- Containerized production workers that control the host Docker socket must set
|
||||||
|
`SPOON_AGENT_HOST_WORKDIR` to the host-side path backing
|
||||||
|
`SPOON_AGENT_WORKDIR`. Docker bind mount source paths are resolved on the host,
|
||||||
|
not inside the worker container.
|
||||||
- `bun smoke:agent-container` checks that the local job image has Node, npm,
|
- `bun smoke:agent-container` checks that the local job image has Node, npm,
|
||||||
Bun, pnpm, yarn, git, ripgrep, jq, Python, OpenCode, and Codex available.
|
Bun, pnpm, yarn, git, ripgrep, jq, Python, OpenCode, and Codex available.
|
||||||
- Old terminal workspaces can be deleted from `Settings -> Worker`; orphaned
|
- Old terminal workspaces can be deleted from `Settings -> Worker`; orphaned
|
||||||
|
|||||||
@@ -474,27 +474,28 @@ not call Infisical.
|
|||||||
<details>
|
<details>
|
||||||
<summary><strong>Convex, storage, and runtime</strong></summary>
|
<summary><strong>Convex, storage, and runtime</strong></summary>
|
||||||
|
|
||||||
| Variable | Used for |
|
| Variable | Used for |
|
||||||
| ----------------------------------- | ----------------------------------------------- |
|
| ----------------------------------- | ----------------------------------------------------------------------------------------------------------- |
|
||||||
| `CONVEX_SELF_HOSTED_URL` | Self-hosted Convex API URL |
|
| `CONVEX_SELF_HOSTED_URL` | Self-hosted Convex API URL |
|
||||||
| `CONVEX_SELF_HOSTED_ADMIN_KEY` | Admin key for deploying/syncing Convex |
|
| `CONVEX_SELF_HOSTED_ADMIN_KEY` | Admin key for deploying/syncing Convex |
|
||||||
| `CONVEX_CLOUD_ORIGIN` | Convex backend origin |
|
| `CONVEX_CLOUD_ORIGIN` | Convex backend origin |
|
||||||
| `CONVEX_SITE_ORIGIN` | Convex site-function origin |
|
| `CONVEX_SITE_ORIGIN` | Convex site-function origin |
|
||||||
| `CONVEX_SITE_URL` | Site URL seen by Convex Auth |
|
| `CONVEX_SITE_URL` | Site URL seen by Convex Auth |
|
||||||
| `POSTGRES_URL` | Convex storage database URL |
|
| `POSTGRES_URL` | Convex storage database URL |
|
||||||
| `SPOON_ENCRYPTION_KEY` | Encryption key for stored secrets/provider auth |
|
| `SPOON_ENCRYPTION_KEY` | Encryption key for stored secrets/provider auth |
|
||||||
| `SPOON_WORKER_TOKEN` | Worker token for Convex worker mutations |
|
| `SPOON_WORKER_TOKEN` | Worker token for Convex worker mutations |
|
||||||
| `SPOON_AGENT_WORKER_URL` | Internal worker HTTP URL used by Next |
|
| `SPOON_AGENT_WORKER_URL` | Internal worker HTTP URL used by Next |
|
||||||
| `SPOON_AGENT_WORKER_HTTP_PORT` | Worker HTTP port |
|
| `SPOON_AGENT_WORKER_HTTP_PORT` | Worker HTTP port |
|
||||||
| `SPOON_AGENT_WORKER_INTERNAL_TOKEN` | Server-only token for Next-to-worker proxy |
|
| `SPOON_AGENT_WORKER_INTERNAL_TOKEN` | Server-only token for Next-to-worker proxy |
|
||||||
| `SPOON_AGENT_JOB_IMAGE` | Agent job container image |
|
| `SPOON_AGENT_JOB_IMAGE` | Agent job container image |
|
||||||
| `SPOON_AGENT_RUNTIME` | Runtime mode, currently Docker/Podman-oriented |
|
| `SPOON_AGENT_RUNTIME` | Runtime mode, currently Docker/Podman-oriented |
|
||||||
| `SPOON_AGENT_CONTAINER_RUNTIME` | Container CLI used by worker, `docker`/`podman` |
|
| `SPOON_AGENT_CONTAINER_RUNTIME` | Container CLI used by worker, `docker`/`podman` |
|
||||||
| `SPOON_AGENT_CONTAINER_ACCESS` | `network` in prod, `host_port` for host dev |
|
| `SPOON_AGENT_CONTAINER_ACCESS` | `network` in prod, `host_port` for host dev |
|
||||||
| `SPOON_AGENT_MAX_CONCURRENT_JOBS` | Worker concurrency limit |
|
| `SPOON_AGENT_MAX_CONCURRENT_JOBS` | Worker concurrency limit |
|
||||||
| `SPOON_AGENT_JOB_TIMEOUT_MS` | Job timeout |
|
| `SPOON_AGENT_JOB_TIMEOUT_MS` | Job timeout |
|
||||||
| `SPOON_AGENT_WORKDIR` | Worker work directory |
|
| `SPOON_AGENT_WORKDIR` | Worker work directory |
|
||||||
| `SPOON_AGENT_NETWORK` | Optional job container network |
|
| `SPOON_AGENT_HOST_WORKDIR` | Host path matching `SPOON_AGENT_WORKDIR` when the worker runs in Docker and controls the host Docker socket |
|
||||||
|
| `SPOON_AGENT_NETWORK` | Optional job container network |
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ const requiredEnv = (name: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const env = {
|
export const env = {
|
||||||
|
buildSha: process.env.SPOON_BUILD_SHA?.trim() ?? 'development',
|
||||||
|
buildCreatedAt: process.env.SPOON_BUILD_CREATED_AT?.trim() ?? 'unknown',
|
||||||
convexUrl:
|
convexUrl:
|
||||||
process.env.NEXT_PUBLIC_CONVEX_URL?.trim() ??
|
process.env.NEXT_PUBLIC_CONVEX_URL?.trim() ??
|
||||||
process.env.CONVEX_SELF_HOSTED_URL?.trim() ??
|
process.env.CONVEX_SELF_HOSTED_URL?.trim() ??
|
||||||
@@ -31,6 +33,7 @@ export const env = {
|
|||||||
jobImage:
|
jobImage:
|
||||||
process.env.SPOON_AGENT_JOB_IMAGE?.trim() ?? 'spoon-agent-job:latest',
|
process.env.SPOON_AGENT_JOB_IMAGE?.trim() ?? 'spoon-agent-job:latest',
|
||||||
workdir: process.env.SPOON_AGENT_WORKDIR?.trim() ?? '.local/agent-work',
|
workdir: process.env.SPOON_AGENT_WORKDIR?.trim() ?? '.local/agent-work',
|
||||||
|
hostWorkdir: process.env.SPOON_AGENT_HOST_WORKDIR?.trim(),
|
||||||
network: process.env.SPOON_AGENT_NETWORK?.trim(),
|
network: process.env.SPOON_AGENT_NETWORK?.trim(),
|
||||||
pollMs: intEnv('SPOON_AGENT_POLL_MS', 5_000),
|
pollMs: intEnv('SPOON_AGENT_POLL_MS', 5_000),
|
||||||
httpPort: intEnv('SPOON_AGENT_WORKER_HTTP_PORT', 3921),
|
httpPort: intEnv('SPOON_AGENT_WORKER_HTTP_PORT', 3921),
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { execa } from 'execa';
|
import { execa } from 'execa';
|
||||||
|
import path from 'node:path';
|
||||||
|
|
||||||
import { env } from '../env';
|
import { env } from '../env';
|
||||||
|
|
||||||
@@ -17,13 +18,25 @@ const networkArgs = () => (env.network ? ['--network', env.network] : []);
|
|||||||
|
|
||||||
const containerRuntime = () => env.containerRuntime;
|
const containerRuntime = () => env.containerRuntime;
|
||||||
|
|
||||||
|
const hostWorkspacePath = (workdir: string) => {
|
||||||
|
if (!env.hostWorkdir) return workdir;
|
||||||
|
const workerRoot = path.resolve(env.workdir);
|
||||||
|
const resolvedWorkdir = path.resolve(workdir);
|
||||||
|
const relative = path.relative(workerRoot, resolvedWorkdir);
|
||||||
|
if (relative.startsWith('..') || path.isAbsolute(relative)) {
|
||||||
|
return workdir;
|
||||||
|
}
|
||||||
|
return path.join(env.hostWorkdir, relative);
|
||||||
|
};
|
||||||
|
|
||||||
export const jobWorkspaceVolumeSpec = (workdir: string) => {
|
export const jobWorkspaceVolumeSpec = (workdir: string) => {
|
||||||
const volumeOptions =
|
const volumeOptions =
|
||||||
env.containerVolumeOptions ??
|
env.containerVolumeOptions ??
|
||||||
(containerRuntime().endsWith('podman') ? 'Z' : undefined);
|
(containerRuntime().endsWith('podman') ? 'Z' : undefined);
|
||||||
|
const source = hostWorkspacePath(workdir);
|
||||||
return volumeOptions
|
return volumeOptions
|
||||||
? `${workdir}:/workspace:${volumeOptions}`
|
? `${source}:/workspace:${volumeOptions}`
|
||||||
: `${workdir}:/workspace`;
|
: `${source}:/workspace`;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const runInJobContainer = async (args: {
|
export const runInJobContainer = async (args: {
|
||||||
|
|||||||
@@ -167,6 +167,9 @@ export const startWorkerServer = () => {
|
|||||||
sendJson(response, 404, { error: 'Not found' });
|
sendJson(response, 404, { error: 'Not found' });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message = error instanceof Error ? error.message : String(error);
|
const message = error instanceof Error ? error.message : String(error);
|
||||||
|
console.error(
|
||||||
|
`Worker HTTP ${request.method ?? 'UNKNOWN'} ${request.url ?? '/'} failed: ${message}`,
|
||||||
|
);
|
||||||
const status =
|
const status =
|
||||||
message === 'Unauthorized'
|
message === 'Unauthorized'
|
||||||
? 401
|
? 401
|
||||||
|
|||||||
@@ -696,6 +696,17 @@ const runCodexTurn = async (args: {
|
|||||||
agentRuntimeMode: 'codex_exec',
|
agentRuntimeMode: 'codex_exec',
|
||||||
codexSessionId: workspace.codexSessionId,
|
codexSessionId: workspace.codexSessionId,
|
||||||
});
|
});
|
||||||
|
const outputFileName = `last-message-${workspace.claim.job._id}.txt`;
|
||||||
|
const outputFileHostPath = path.join(
|
||||||
|
workspace.workdir,
|
||||||
|
'.codex',
|
||||||
|
outputFileName,
|
||||||
|
);
|
||||||
|
const outputFileContainerPath = path.posix.join(
|
||||||
|
codexContainerWorkspace,
|
||||||
|
'.codex',
|
||||||
|
outputFileName,
|
||||||
|
);
|
||||||
const command = workspace.codexSessionId
|
const command = workspace.codexSessionId
|
||||||
? [
|
? [
|
||||||
'codex',
|
'codex',
|
||||||
@@ -704,6 +715,8 @@ const runCodexTurn = async (args: {
|
|||||||
'--json',
|
'--json',
|
||||||
...codexModelArgs(workspace.claim),
|
...codexModelArgs(workspace.claim),
|
||||||
'--dangerously-bypass-approvals-and-sandbox',
|
'--dangerously-bypass-approvals-and-sandbox',
|
||||||
|
'--output-last-message',
|
||||||
|
outputFileContainerPath,
|
||||||
workspace.codexSessionId,
|
workspace.codexSessionId,
|
||||||
prompt,
|
prompt,
|
||||||
]
|
]
|
||||||
@@ -713,6 +726,8 @@ const runCodexTurn = async (args: {
|
|||||||
'--json',
|
'--json',
|
||||||
...codexModelArgs(workspace.claim),
|
...codexModelArgs(workspace.claim),
|
||||||
'--dangerously-bypass-approvals-and-sandbox',
|
'--dangerously-bypass-approvals-and-sandbox',
|
||||||
|
'--output-last-message',
|
||||||
|
outputFileContainerPath,
|
||||||
'--cd',
|
'--cd',
|
||||||
codexContainerRepo,
|
codexContainerRepo,
|
||||||
prompt,
|
prompt,
|
||||||
@@ -756,9 +771,48 @@ const runCodexTurn = async (args: {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
await appendEvent(
|
||||||
|
workspace.claim.job._id,
|
||||||
|
'info',
|
||||||
|
'plan',
|
||||||
|
`Codex CLI exited with code ${result.exitCode}; captured output length ${result.output.length}; assistant length ${assistantContent.value.length}.`,
|
||||||
|
);
|
||||||
if (result.exitCode !== 0) {
|
if (result.exitCode !== 0) {
|
||||||
throw new Error(`codex failed:\n${result.output}`);
|
throw new Error(`codex failed:\n${result.output}`);
|
||||||
}
|
}
|
||||||
|
if (!assistantContent.value.trim()) {
|
||||||
|
try {
|
||||||
|
const lastMessage = await readFile(outputFileHostPath, 'utf8');
|
||||||
|
if (lastMessage.trim()) {
|
||||||
|
assistantContent.value = truncate(
|
||||||
|
workspace.redact(lastMessage.trim()),
|
||||||
|
40_000,
|
||||||
|
);
|
||||||
|
await updateMessage({
|
||||||
|
messageId: assistantMessageId,
|
||||||
|
content: assistantContent.value,
|
||||||
|
status: 'streaming',
|
||||||
|
});
|
||||||
|
await appendEvent(
|
||||||
|
workspace.claim.job._id,
|
||||||
|
'info',
|
||||||
|
'plan',
|
||||||
|
`Recovered assistant response from Codex output file ${outputFileName}.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
const code = error && typeof error === 'object' ? 'code' in error : false;
|
||||||
|
if (!code || (error as { code?: string }).code !== 'ENOENT') {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
await appendEvent(
|
||||||
|
workspace.claim.job._id,
|
||||||
|
'warn',
|
||||||
|
'plan',
|
||||||
|
`Codex output file ${outputFileName} was not created.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const runOpenCodeTurn = async (args: {
|
const runOpenCodeTurn = async (args: {
|
||||||
@@ -1249,6 +1303,12 @@ const runClaim = async (claim: Claim) => {
|
|||||||
'Workspace is ready. You can browse files, edit manually, run commands, or send messages to the agent.',
|
'Workspace is ready. You can browse files, edit manually, run commands, or send messages to the agent.',
|
||||||
});
|
});
|
||||||
await appendEvent(jobId, 'info', 'plan', 'Interactive workspace is ready.');
|
await appendEvent(jobId, 'info', 'plan', 'Interactive workspace is ready.');
|
||||||
|
await appendEvent(
|
||||||
|
jobId,
|
||||||
|
'info',
|
||||||
|
'plan',
|
||||||
|
`Worker runtime ${env.workerId} build ${env.buildSha} (${env.buildCreatedAt}).`,
|
||||||
|
);
|
||||||
|
|
||||||
await sendWorkspaceMessage(jobId, systemPromptForJob(claim), {
|
await sendWorkspaceMessage(jobId, systemPromptForJob(claim), {
|
||||||
recordUserMessage: false,
|
recordUserMessage: false,
|
||||||
@@ -1488,6 +1548,9 @@ export const sendWorkspaceMessage = async (
|
|||||||
assistantMessageId,
|
assistantMessageId,
|
||||||
assistantContent,
|
assistantContent,
|
||||||
});
|
});
|
||||||
|
console.log(
|
||||||
|
`Codex turn completed for job ${claim.job._id}; response length=${assistantContent.value.length}`,
|
||||||
|
);
|
||||||
} else if (env.runtime === 'docker') {
|
} else if (env.runtime === 'docker') {
|
||||||
await appendEvent(
|
await appendEvent(
|
||||||
claim.job._id,
|
claim.job._id,
|
||||||
@@ -1526,6 +1589,9 @@ export const sendWorkspaceMessage = async (
|
|||||||
}
|
}
|
||||||
if (isCodexLoginProfile(claim)) {
|
if (isCodexLoginProfile(claim)) {
|
||||||
if (!assistantContent.value.trim()) {
|
if (!assistantContent.value.trim()) {
|
||||||
|
console.error(
|
||||||
|
`Codex completed without producing an assistant response for job ${claim.job._id}.`,
|
||||||
|
);
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Codex completed without producing an assistant response.',
|
'Codex completed without producing an assistant response.',
|
||||||
);
|
);
|
||||||
@@ -1570,6 +1636,7 @@ export const sendWorkspaceMessage = async (
|
|||||||
workspace.resolveTurn?.();
|
workspace.resolveTurn?.();
|
||||||
workspace.resolveTurn = undefined;
|
workspace.resolveTurn = undefined;
|
||||||
const message = error instanceof Error ? error.message : String(error);
|
const message = error instanceof Error ? error.message : String(error);
|
||||||
|
console.error(`Agent turn failed for job ${claim.job._id}: ${message}`);
|
||||||
await appendEvent(
|
await appendEvent(
|
||||||
claim.job._id,
|
claim.job._id,
|
||||||
'error',
|
'error',
|
||||||
@@ -1692,6 +1759,8 @@ export const getWorkerHealth = async () => {
|
|||||||
const containerNames = await listWorkspaceContainerNames('spoon-agent-job-');
|
const containerNames = await listWorkspaceContainerNames('spoon-agent-job-');
|
||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
|
buildSha: env.buildSha,
|
||||||
|
buildCreatedAt: env.buildCreatedAt,
|
||||||
workerId: env.workerId,
|
workerId: env.workerId,
|
||||||
convexUrl: env.convexUrl,
|
convexUrl: env.convexUrl,
|
||||||
runtime: env.runtime,
|
runtime: env.runtime,
|
||||||
@@ -1699,6 +1768,7 @@ export const getWorkerHealth = async () => {
|
|||||||
containerAccess: env.containerAccess,
|
containerAccess: env.containerAccess,
|
||||||
jobImage: env.jobImage,
|
jobImage: env.jobImage,
|
||||||
workdir: env.workdir,
|
workdir: env.workdir,
|
||||||
|
hostWorkdir: env.hostWorkdir,
|
||||||
network: env.network,
|
network: env.network,
|
||||||
httpPort: env.httpPort,
|
httpPort: env.httpPort,
|
||||||
maxConcurrentJobs: env.maxConcurrentJobs,
|
maxConcurrentJobs: env.maxConcurrentJobs,
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
FROM docker.io/oven/bun:1.3.10
|
FROM docker.io/oven/bun:1.3.10
|
||||||
|
|
||||||
|
ARG SPOON_BUILD_SHA=development
|
||||||
|
ARG SPOON_BUILD_CREATED_AT=unknown
|
||||||
|
|
||||||
|
ENV SPOON_BUILD_SHA=${SPOON_BUILD_SHA}
|
||||||
|
ENV SPOON_BUILD_CREATED_AT=${SPOON_BUILD_CREATED_AT}
|
||||||
|
|
||||||
RUN apt-get update \
|
RUN apt-get update \
|
||||||
&& apt-get install -y --no-install-recommends \
|
&& apt-get install -y --no-install-recommends \
|
||||||
bash \
|
bash \
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ set -euo pipefail
|
|||||||
|
|
||||||
ROOT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
|
ROOT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
RUNTIME="${SPOON_AGENT_CONTAINER_RUNTIME:-}"
|
RUNTIME="${SPOON_AGENT_CONTAINER_RUNTIME:-}"
|
||||||
|
BUILD_SHA="${SPOON_BUILD_SHA:-$(git -C "$ROOT_DIR" rev-parse --short=12 HEAD 2>/dev/null || printf development)}"
|
||||||
|
BUILD_CREATED_AT="${SPOON_BUILD_CREATED_AT:-$(date -u +%Y-%m-%dT%H:%M:%SZ)}"
|
||||||
|
|
||||||
if [[ -z "$RUNTIME" ]]; then
|
if [[ -z "$RUNTIME" ]]; then
|
||||||
if command -v podman >/dev/null 2>&1; then
|
if command -v podman >/dev/null 2>&1; then
|
||||||
@@ -15,5 +17,10 @@ if [[ -z "$RUNTIME" ]]; then
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
"$RUNTIME" build -f "$ROOT_DIR/docker/agent-worker.Dockerfile" -t spoon-agent-worker:latest "$ROOT_DIR"
|
"$RUNTIME" build \
|
||||||
|
--build-arg "SPOON_BUILD_SHA=$BUILD_SHA" \
|
||||||
|
--build-arg "SPOON_BUILD_CREATED_AT=$BUILD_CREATED_AT" \
|
||||||
|
-f "$ROOT_DIR/docker/agent-worker.Dockerfile" \
|
||||||
|
-t spoon-agent-worker:latest \
|
||||||
|
"$ROOT_DIR"
|
||||||
"$RUNTIME" build -f "$ROOT_DIR/docker/agent-job.Dockerfile" -t spoon-agent-job:latest "$ROOT_DIR"
|
"$RUNTIME" build -f "$ROOT_DIR/docker/agent-job.Dockerfile" -t spoon-agent-job:latest "$ROOT_DIR"
|
||||||
|
|||||||
@@ -48,11 +48,14 @@
|
|||||||
"SPOON_AGENT_MAX_CONCURRENT_JOBS",
|
"SPOON_AGENT_MAX_CONCURRENT_JOBS",
|
||||||
"SPOON_AGENT_JOB_TIMEOUT_MS",
|
"SPOON_AGENT_JOB_TIMEOUT_MS",
|
||||||
"SPOON_AGENT_WORKDIR",
|
"SPOON_AGENT_WORKDIR",
|
||||||
|
"SPOON_AGENT_HOST_WORKDIR",
|
||||||
"SPOON_AGENT_NETWORK",
|
"SPOON_AGENT_NETWORK",
|
||||||
"SPOON_AGENT_POLL_MS",
|
"SPOON_AGENT_POLL_MS",
|
||||||
"SPOON_AGENT_WORKER_URL",
|
"SPOON_AGENT_WORKER_URL",
|
||||||
"SPOON_AGENT_WORKER_HTTP_PORT",
|
"SPOON_AGENT_WORKER_HTTP_PORT",
|
||||||
"SPOON_AGENT_WORKER_INTERNAL_TOKEN",
|
"SPOON_AGENT_WORKER_INTERNAL_TOKEN",
|
||||||
|
"SPOON_BUILD_SHA",
|
||||||
|
"SPOON_BUILD_CREATED_AT",
|
||||||
"SKIP_E2E",
|
"SKIP_E2E",
|
||||||
"BASE_URL",
|
"BASE_URL",
|
||||||
"NETWORK",
|
"NETWORK",
|
||||||
|
|||||||
Reference in New Issue
Block a user