Merge remote-tracking branch 'public-gh/master'

* public-gh/master:
  fix(ui): wrap failed run card actions on mobile
  feat(codex): add gpt-5.4 to codex_local model list
  persist paperclip data in a named volume
  add support to `cursor` and `opencode` in containerized instances
  force `@types/node@24` in the server
  Add artifact-check to fail fast on broken builds
  remove an insecure default auth secret
  expose `PAPERCLIP_ALLOWED_HOSTNAMES` in compose files
  wait for a health db
  update lock file
  update typing to node v24 from v20
  add missing `openclaw` adapter from deps stage
  fix incorrect pkg scope
  update docker base image
  move docker into `authenticated` deployment mode
This commit is contained in:
Dotta
2026-03-06 10:14:11 -06:00
15 changed files with 231 additions and 28 deletions

View File

@@ -1,4 +1,4 @@
FROM node:20-bookworm-slim AS base FROM node:lts-trixie-slim AS base
RUN apt-get update \ RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates curl git \ && apt-get install -y --no-install-recommends ca-certificates curl git \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
@@ -15,14 +15,18 @@ COPY packages/db/package.json packages/db/
COPY packages/adapter-utils/package.json packages/adapter-utils/ COPY packages/adapter-utils/package.json packages/adapter-utils/
COPY packages/adapters/claude-local/package.json packages/adapters/claude-local/ COPY packages/adapters/claude-local/package.json packages/adapters/claude-local/
COPY packages/adapters/codex-local/package.json packages/adapters/codex-local/ COPY packages/adapters/codex-local/package.json packages/adapters/codex-local/
COPY packages/adapters/cursor-local/package.json packages/adapters/cursor-local/
COPY packages/adapters/openclaw/package.json packages/adapters/openclaw/
COPY packages/adapters/opencode-local/package.json packages/adapters/opencode-local/
RUN pnpm install --frozen-lockfile RUN pnpm install --frozen-lockfile
FROM base AS build FROM base AS build
WORKDIR /app WORKDIR /app
COPY --from=deps /app /app COPY --from=deps /app /app
COPY . . COPY . .
RUN pnpm --filter @paperclip/ui build RUN pnpm --filter @paperclipai/ui build
RUN pnpm --filter @paperclip/server build RUN pnpm --filter @paperclipai/server build
RUN test -f server/dist/index.js || (echo "ERROR: server build output missing" && exit 1)
FROM base AS production FROM base AS production
WORKDIR /app WORKDIR /app
@@ -37,7 +41,7 @@ ENV NODE_ENV=production \
PAPERCLIP_HOME=/paperclip \ PAPERCLIP_HOME=/paperclip \
PAPERCLIP_INSTANCE_ID=default \ PAPERCLIP_INSTANCE_ID=default \
PAPERCLIP_CONFIG=/paperclip/instances/default/config.json \ PAPERCLIP_CONFIG=/paperclip/instances/default/config.json \
PAPERCLIP_DEPLOYMENT_MODE=local_trusted \ PAPERCLIP_DEPLOYMENT_MODE=authenticated \
PAPERCLIP_DEPLOYMENT_EXPOSURE=private PAPERCLIP_DEPLOYMENT_EXPOSURE=private
VOLUME ["/paperclip"] VOLUME ["/paperclip"]

View File

@@ -10,5 +10,9 @@ services:
PAPERCLIP_HOME: "/paperclip" PAPERCLIP_HOME: "/paperclip"
OPENAI_API_KEY: "${OPENAI_API_KEY:-}" OPENAI_API_KEY: "${OPENAI_API_KEY:-}"
ANTHROPIC_API_KEY: "${ANTHROPIC_API_KEY:-}" ANTHROPIC_API_KEY: "${ANTHROPIC_API_KEY:-}"
PAPERCLIP_DEPLOYMENT_MODE: "authenticated"
PAPERCLIP_DEPLOYMENT_EXPOSURE: "private"
PAPERCLIP_ALLOWED_HOSTNAMES: "${PAPERCLIP_ALLOWED_HOSTNAMES:-localhost}"
BETTER_AUTH_SECRET: "${BETTER_AUTH_SECRET:?BETTER_AUTH_SECRET must be set}"
volumes: volumes:
- "${PAPERCLIP_DATA_DIR:-./data/docker-paperclip}:/paperclip" - "${PAPERCLIP_DATA_DIR:-./data/docker-paperclip}:/paperclip"

View File

@@ -5,6 +5,11 @@ services:
POSTGRES_USER: paperclip POSTGRES_USER: paperclip
POSTGRES_PASSWORD: paperclip POSTGRES_PASSWORD: paperclip
POSTGRES_DB: paperclip POSTGRES_DB: paperclip
healthcheck:
test: ["CMD-SHELL", "pg_isready -U paperclip -d paperclip"]
interval: 2s
timeout: 5s
retries: 30
ports: ports:
- "5432:5432" - "5432:5432"
volumes: volumes:
@@ -18,8 +23,16 @@ services:
DATABASE_URL: postgres://paperclip:paperclip@db:5432/paperclip DATABASE_URL: postgres://paperclip:paperclip@db:5432/paperclip
PORT: "3100" PORT: "3100"
SERVE_UI: "true" SERVE_UI: "true"
PAPERCLIP_DEPLOYMENT_MODE: "authenticated"
PAPERCLIP_DEPLOYMENT_EXPOSURE: "private"
PAPERCLIP_ALLOWED_HOSTNAMES: "${PAPERCLIP_ALLOWED_HOSTNAMES:-localhost}"
BETTER_AUTH_SECRET: "${BETTER_AUTH_SECRET:?BETTER_AUTH_SECRET must be set}"
volumes:
- paperclip-data:/paperclip
depends_on: depends_on:
- db db:
condition: service_healthy
volumes: volumes:
pgdata: pgdata:
paperclip-data:

View File

@@ -30,6 +30,7 @@
"typecheck": "tsc --noEmit" "typecheck": "tsc --noEmit"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^24.6.0",
"typescript": "^5.7.3" "typescript": "^5.7.3"
} }
} }

View File

@@ -15,6 +15,14 @@ interface RunningProcess {
graceSec: number; graceSec: number;
} }
type ChildProcessWithEvents = ChildProcess & {
on(event: "error", listener: (err: Error) => void): ChildProcess;
on(
event: "close",
listener: (code: number | null, signal: NodeJS.Signals | null) => void,
): ChildProcess;
};
export const runningProcesses = new Map<string, RunningProcess>(); export const runningProcesses = new Map<string, RunningProcess>();
export const MAX_CAPTURE_BYTES = 4 * 1024 * 1024; export const MAX_CAPTURE_BYTES = 4 * 1024 * 1024;
export const MAX_EXCERPT_BYTES = 32 * 1024; export const MAX_EXCERPT_BYTES = 32 * 1024;
@@ -217,7 +225,7 @@ export async function runChildProcess(
env: mergedEnv, env: mergedEnv,
shell: false, shell: false,
stdio: [opts.stdin != null ? "pipe" : "ignore", "pipe", "pipe"], stdio: [opts.stdin != null ? "pipe" : "ignore", "pipe", "pipe"],
}); }) as ChildProcessWithEvents;
if (opts.stdin != null && child.stdin) { if (opts.stdin != null && child.stdin) {
child.stdin.write(opts.stdin); child.stdin.write(opts.stdin);
@@ -244,7 +252,7 @@ export async function runChildProcess(
}, opts.timeoutSec * 1000) }, opts.timeoutSec * 1000)
: null; : null;
child.stdout?.on("data", (chunk) => { child.stdout?.on("data", (chunk: unknown) => {
const text = String(chunk); const text = String(chunk);
stdout = appendWithCap(stdout, text); stdout = appendWithCap(stdout, text);
logChain = logChain logChain = logChain
@@ -252,7 +260,7 @@ export async function runChildProcess(
.catch((err) => onLogError(err, runId, "failed to append stdout log chunk")); .catch((err) => onLogError(err, runId, "failed to append stdout log chunk"));
}); });
child.stderr?.on("data", (chunk) => { child.stderr?.on("data", (chunk: unknown) => {
const text = String(chunk); const text = String(chunk);
stderr = appendWithCap(stderr, text); stderr = appendWithCap(stderr, text);
logChain = logChain logChain = logChain
@@ -260,7 +268,7 @@ export async function runChildProcess(
.catch((err) => onLogError(err, runId, "failed to append stderr log chunk")); .catch((err) => onLogError(err, runId, "failed to append stderr log chunk"));
}); });
child.on("error", (err) => { child.on("error", (err: Error) => {
if (timeout) clearTimeout(timeout); if (timeout) clearTimeout(timeout);
runningProcesses.delete(runId); runningProcesses.delete(runId);
const errno = (err as NodeJS.ErrnoException).code; const errno = (err as NodeJS.ErrnoException).code;
@@ -272,7 +280,7 @@ export async function runChildProcess(
reject(new Error(msg)); reject(new Error(msg));
}); });
child.on("close", (code, signal) => { child.on("close", (code: number | null, signal: NodeJS.Signals | null) => {
if (timeout) clearTimeout(timeout); if (timeout) clearTimeout(timeout);
runningProcesses.delete(runId); runningProcesses.delete(runId);
void logChain.finally(() => { void logChain.finally(() => {

View File

@@ -45,6 +45,7 @@
"picocolors": "^1.1.1" "picocolors": "^1.1.1"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^24.6.0",
"typescript": "^5.7.3" "typescript": "^5.7.3"
} }
} }

View File

@@ -45,6 +45,7 @@
"picocolors": "^1.1.1" "picocolors": "^1.1.1"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^24.6.0",
"typescript": "^5.7.3" "typescript": "^5.7.3"
} }
} }

View File

@@ -4,6 +4,7 @@ export const DEFAULT_CODEX_LOCAL_MODEL = "gpt-5.3-codex";
export const DEFAULT_CODEX_LOCAL_BYPASS_APPROVALS_AND_SANDBOX = true; export const DEFAULT_CODEX_LOCAL_BYPASS_APPROVALS_AND_SANDBOX = true;
export const models = [ export const models = [
{ id: "gpt-5.4", label: "gpt-5.4" },
{ id: DEFAULT_CODEX_LOCAL_MODEL, label: DEFAULT_CODEX_LOCAL_MODEL }, { id: DEFAULT_CODEX_LOCAL_MODEL, label: DEFAULT_CODEX_LOCAL_MODEL },
{ id: "gpt-5.3-codex-spark", label: "gpt-5.3-codex-spark" }, { id: "gpt-5.3-codex-spark", label: "gpt-5.3-codex-spark" },
{ id: "gpt-5", label: "gpt-5" }, { id: "gpt-5", label: "gpt-5" },

View File

@@ -44,6 +44,7 @@
"picocolors": "^1.1.1" "picocolors": "^1.1.1"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^24.6.0",
"typescript": "^5.7.3" "typescript": "^5.7.3"
} }
} }

View File

@@ -38,6 +38,7 @@
"postgres": "^3.4.5" "postgres": "^3.4.5"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^24.6.0",
"drizzle-kit": "^0.31.9", "drizzle-kit": "^0.31.9",
"tsx": "^4.19.2", "tsx": "^4.19.2",
"typescript": "^5.7.3", "typescript": "^5.7.3",

148
pnpm-lock.yaml generated
View File

@@ -78,6 +78,9 @@ importers:
packages/adapter-utils: packages/adapter-utils:
devDependencies: devDependencies:
'@types/node':
specifier: ^24.6.0
version: 24.11.0
typescript: typescript:
specifier: ^5.7.3 specifier: ^5.7.3
version: 5.9.3 version: 5.9.3
@@ -91,6 +94,9 @@ importers:
specifier: ^1.1.1 specifier: ^1.1.1
version: 1.1.1 version: 1.1.1
devDependencies: devDependencies:
'@types/node':
specifier: ^24.6.0
version: 24.11.0
typescript: typescript:
specifier: ^5.7.3 specifier: ^5.7.3
version: 5.9.3 version: 5.9.3
@@ -104,6 +110,9 @@ importers:
specifier: ^1.1.1 specifier: ^1.1.1
version: 1.1.1 version: 1.1.1
devDependencies: devDependencies:
'@types/node':
specifier: ^24.6.0
version: 24.11.0
typescript: typescript:
specifier: ^5.7.3 specifier: ^5.7.3
version: 5.9.3 version: 5.9.3
@@ -130,6 +139,9 @@ importers:
specifier: ^1.1.1 specifier: ^1.1.1
version: 1.1.1 version: 1.1.1
devDependencies: devDependencies:
'@types/node':
specifier: ^24.6.0
version: 24.11.0
typescript: typescript:
specifier: ^5.7.3 specifier: ^5.7.3
version: 5.9.3 version: 5.9.3
@@ -159,6 +171,9 @@ importers:
specifier: ^3.4.5 specifier: ^3.4.5
version: 3.4.8 version: 3.4.8
devDependencies: devDependencies:
'@types/node':
specifier: ^24.6.0
version: 24.11.0
drizzle-kit: drizzle-kit:
specifier: ^0.31.9 specifier: ^0.31.9
version: 0.31.9 version: 0.31.9
@@ -170,7 +185,7 @@ importers:
version: 5.9.3 version: 5.9.3
vitest: vitest:
specifier: ^3.0.5 specifier: ^3.0.5
version: 3.2.4(@types/debug@4.1.12)(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0) version: 3.2.4(@types/debug@4.1.12)(@types/node@24.11.0)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)
packages/shared: packages/shared:
dependencies: dependencies:
@@ -213,7 +228,7 @@ importers:
version: link:../packages/shared version: link:../packages/shared
better-auth: better-auth:
specifier: 1.4.18 specifier: 1.4.18
version: 1.4.18(drizzle-kit@0.31.9)(drizzle-orm@0.38.4(@electric-sql/pglite@0.3.15)(@types/react@19.2.14)(kysely@0.28.11)(pg@8.18.0)(postgres@3.4.8)(react@19.2.4))(pg@8.18.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)) version: 1.4.18(drizzle-kit@0.31.9)(drizzle-orm@0.38.4(@electric-sql/pglite@0.3.15)(@types/react@19.2.14)(kysely@0.28.11)(pg@8.18.0)(postgres@3.4.8)(react@19.2.4))(pg@8.18.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.11.0)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0))
detect-port: detect-port:
specifier: ^2.1.0 specifier: ^2.1.0
version: 2.1.0 version: 2.1.0
@@ -260,9 +275,15 @@ importers:
'@types/multer': '@types/multer':
specifier: ^2.0.0 specifier: ^2.0.0
version: 2.0.0 version: 2.0.0
'@types/node':
specifier: ^24.6.0
version: 24.11.0
'@types/supertest': '@types/supertest':
specifier: ^6.0.2 specifier: ^6.0.2
version: 6.0.3 version: 6.0.3
'@types/ws':
specifier: ^8.18.1
version: 8.18.1
supertest: supertest:
specifier: ^7.0.0 specifier: ^7.0.0
version: 7.2.2 version: 7.2.2
@@ -274,10 +295,10 @@ importers:
version: 5.9.3 version: 5.9.3
vite: vite:
specifier: ^6.1.0 specifier: ^6.1.0
version: 6.4.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0) version: 6.4.1(@types/node@24.11.0)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)
vitest: vitest:
specifier: ^3.0.5 specifier: ^3.0.5
version: 3.2.4(@types/debug@4.1.12)(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0) version: 3.2.4(@types/debug@4.1.12)(@types/node@24.11.0)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)
ui: ui:
dependencies: dependencies:
@@ -2816,6 +2837,9 @@ packages:
'@types/node@22.19.11': '@types/node@22.19.11':
resolution: {integrity: sha512-BH7YwL6rA93ReqeQS1c4bsPpcfOmJasG+Fkr6Y59q83f9M1WcBRHR2vM+P9eOisYRcN3ujQoiZY8uk5W+1WL8w==} resolution: {integrity: sha512-BH7YwL6rA93ReqeQS1c4bsPpcfOmJasG+Fkr6Y59q83f9M1WcBRHR2vM+P9eOisYRcN3ujQoiZY8uk5W+1WL8w==}
'@types/node@24.11.0':
resolution: {integrity: sha512-fPxQqz4VTgPI/IQ+lj9r0h+fDR66bzoeMGHp8ASee+32OSGIkeASsoZuJixsQoVef1QJbeubcPBxKk22QVoWdw==}
'@types/node@25.2.3': '@types/node@25.2.3':
resolution: {integrity: sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ==} resolution: {integrity: sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ==}
@@ -2851,6 +2875,9 @@ packages:
'@types/unist@3.0.3': '@types/unist@3.0.3':
resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==}
'@types/ws@8.18.1':
resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==}
'@ungap/structured-clone@1.3.0': '@ungap/structured-clone@1.3.0':
resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
@@ -8194,6 +8221,10 @@ snapshots:
dependencies: dependencies:
undici-types: 6.21.0 undici-types: 6.21.0
'@types/node@24.11.0':
dependencies:
undici-types: 7.16.0
'@types/node@25.2.3': '@types/node@25.2.3':
dependencies: dependencies:
undici-types: 7.16.0 undici-types: 7.16.0
@@ -8235,6 +8266,10 @@ snapshots:
'@types/unist@3.0.3': {} '@types/unist@3.0.3': {}
'@types/ws@8.18.1':
dependencies:
'@types/node': 25.2.3
'@ungap/structured-clone@1.3.0': {} '@ungap/structured-clone@1.3.0': {}
'@vitejs/plugin-react@4.7.0(vite@6.4.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0))': '@vitejs/plugin-react@4.7.0(vite@6.4.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0))':
@@ -8257,6 +8292,14 @@ snapshots:
chai: 5.3.3 chai: 5.3.3
tinyrainbow: 2.0.0 tinyrainbow: 2.0.0
'@vitest/mocker@3.2.4(vite@7.3.1(@types/node@24.11.0)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0))':
dependencies:
'@vitest/spy': 3.2.4
estree-walker: 3.0.3
magic-string: 0.30.21
optionalDependencies:
vite: 7.3.1(@types/node@24.11.0)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)
'@vitest/mocker@3.2.4(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0))': '@vitest/mocker@3.2.4(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0))':
dependencies: dependencies:
'@vitest/spy': 3.2.4 '@vitest/spy': 3.2.4
@@ -8340,7 +8383,7 @@ snapshots:
baseline-browser-mapping@2.9.19: {} baseline-browser-mapping@2.9.19: {}
better-auth@1.4.18(drizzle-kit@0.31.9)(drizzle-orm@0.38.4(@electric-sql/pglite@0.3.15)(@types/react@19.2.14)(kysely@0.28.11)(pg@8.18.0)(postgres@3.4.8)(react@19.2.4))(pg@8.18.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)): better-auth@1.4.18(drizzle-kit@0.31.9)(drizzle-orm@0.38.4(@electric-sql/pglite@0.3.15)(@types/react@19.2.14)(kysely@0.28.11)(pg@8.18.0)(postgres@3.4.8)(react@19.2.4))(pg@8.18.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.11.0)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)):
dependencies: dependencies:
'@better-auth/core': 1.4.18(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@3.25.76))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.0) '@better-auth/core': 1.4.18(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@3.25.76))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.0)
'@better-auth/telemetry': 1.4.18(@better-auth/core@1.4.18(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@3.25.76))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.0)) '@better-auth/telemetry': 1.4.18(@better-auth/core@1.4.18(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@3.25.76))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.0))
@@ -8360,7 +8403,7 @@ snapshots:
pg: 8.18.0 pg: 8.18.0
react: 19.2.4 react: 19.2.4
react-dom: 19.2.4(react@19.2.4) react-dom: 19.2.4(react@19.2.4)
vitest: 3.2.4(@types/debug@4.1.12)(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0) vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.11.0)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)
better-call@1.1.8(zod@4.3.6): better-call@1.1.8(zod@4.3.6):
dependencies: dependencies:
@@ -10601,6 +10644,27 @@ snapshots:
'@types/unist': 3.0.3 '@types/unist': 3.0.3
vfile-message: 4.0.3 vfile-message: 4.0.3
vite-node@3.2.4(@types/node@24.11.0)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0):
dependencies:
cac: 6.7.14
debug: 4.4.3
es-module-lexer: 1.7.0
pathe: 2.0.3
vite: 6.4.1(@types/node@24.11.0)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)
transitivePeerDependencies:
- '@types/node'
- jiti
- less
- lightningcss
- sass
- sass-embedded
- stylus
- sugarss
- supports-color
- terser
- tsx
- yaml
vite-node@3.2.4(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0): vite-node@3.2.4(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0):
dependencies: dependencies:
cac: 6.7.14 cac: 6.7.14
@@ -10622,6 +10686,21 @@ snapshots:
- tsx - tsx
- yaml - yaml
vite@6.4.1(@types/node@24.11.0)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0):
dependencies:
esbuild: 0.25.12
fdir: 6.5.0(picomatch@4.0.3)
picomatch: 4.0.3
postcss: 8.5.6
rollup: 4.57.1
tinyglobby: 0.2.15
optionalDependencies:
'@types/node': 24.11.0
fsevents: 2.3.3
jiti: 2.6.1
lightningcss: 1.30.2
tsx: 4.21.0
vite@6.4.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0): vite@6.4.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0):
dependencies: dependencies:
esbuild: 0.25.12 esbuild: 0.25.12
@@ -10637,6 +10716,21 @@ snapshots:
lightningcss: 1.30.2 lightningcss: 1.30.2
tsx: 4.21.0 tsx: 4.21.0
vite@7.3.1(@types/node@24.11.0)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0):
dependencies:
esbuild: 0.27.3
fdir: 6.5.0(picomatch@4.0.3)
picomatch: 4.0.3
postcss: 8.5.6
rollup: 4.57.1
tinyglobby: 0.2.15
optionalDependencies:
'@types/node': 24.11.0
fsevents: 2.3.3
jiti: 2.6.1
lightningcss: 1.30.2
tsx: 4.21.0
vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0): vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0):
dependencies: dependencies:
esbuild: 0.27.3 esbuild: 0.27.3
@@ -10652,6 +10746,48 @@ snapshots:
lightningcss: 1.30.2 lightningcss: 1.30.2
tsx: 4.21.0 tsx: 4.21.0
vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.11.0)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0):
dependencies:
'@types/chai': 5.2.3
'@vitest/expect': 3.2.4
'@vitest/mocker': 3.2.4(vite@7.3.1(@types/node@24.11.0)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0))
'@vitest/pretty-format': 3.2.4
'@vitest/runner': 3.2.4
'@vitest/snapshot': 3.2.4
'@vitest/spy': 3.2.4
'@vitest/utils': 3.2.4
chai: 5.3.3
debug: 4.4.3
expect-type: 1.3.0
magic-string: 0.30.21
pathe: 2.0.3
picomatch: 4.0.3
std-env: 3.10.0
tinybench: 2.9.0
tinyexec: 0.3.2
tinyglobby: 0.2.15
tinypool: 1.1.1
tinyrainbow: 2.0.0
vite: 7.3.1(@types/node@24.11.0)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)
vite-node: 3.2.4(@types/node@24.11.0)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)
why-is-node-running: 2.3.0
optionalDependencies:
'@types/debug': 4.1.12
'@types/node': 24.11.0
transitivePeerDependencies:
- jiti
- less
- lightningcss
- msw
- sass
- sass-embedded
- stylus
- sugarss
- supports-color
- terser
- tsx
- yaml
vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0): vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0):
dependencies: dependencies:
'@types/chai': 5.2.3 '@types/chai': 5.2.3

View File

@@ -57,7 +57,9 @@
"@types/express": "^5.0.0", "@types/express": "^5.0.0",
"@types/express-serve-static-core": "^5.0.0", "@types/express-serve-static-core": "^5.0.0",
"@types/multer": "^2.0.0", "@types/multer": "^2.0.0",
"@types/node": "^24.6.0",
"@types/supertest": "^6.0.2", "@types/supertest": "^6.0.2",
"@types/ws": "^8.18.1",
"supertest": "^7.0.0", "supertest": "^7.0.0",
"tsx": "^4.19.2", "tsx": "^4.19.2",
"typescript": "^5.7.3", "typescript": "^5.7.3",

View File

@@ -444,7 +444,7 @@ const app = await createApp(db as any, {
betterAuthHandler, betterAuthHandler,
resolveSession, resolveSession,
}); });
const server = createServer(app); const server = createServer(app as unknown as Parameters<typeof createServer>[0]);
const listenPort = await detectPort(config.port); const listenPort = await detectPort(config.port);
if (listenPort !== config.port) { if (listenPort !== config.port) {

View File

@@ -1,15 +1,45 @@
import { createHash } from "node:crypto"; import { createHash } from "node:crypto";
import type { IncomingMessage, Server as HttpServer } from "node:http"; import type { IncomingMessage, Server as HttpServer } from "node:http";
import { createRequire } from "node:module";
import type { Duplex } from "node:stream"; import type { Duplex } from "node:stream";
import { and, eq, isNull } from "drizzle-orm"; import { and, eq, isNull } from "drizzle-orm";
import type { Db } from "@paperclipai/db"; import type { Db } from "@paperclipai/db";
import { agentApiKeys, companyMemberships, instanceUserRoles } from "@paperclipai/db"; import { agentApiKeys, companyMemberships, instanceUserRoles } from "@paperclipai/db";
import type { DeploymentMode } from "@paperclipai/shared"; import type { DeploymentMode } from "@paperclipai/shared";
import { WebSocket, WebSocketServer } from "ws";
import type { BetterAuthSessionResult } from "../auth/better-auth.js"; import type { BetterAuthSessionResult } from "../auth/better-auth.js";
import { logger } from "../middleware/logger.js"; import { logger } from "../middleware/logger.js";
import { subscribeCompanyLiveEvents } from "../services/live-events.js"; import { subscribeCompanyLiveEvents } from "../services/live-events.js";
interface WsSocket {
readyState: number;
ping(): void;
send(data: string): void;
terminate(): void;
close(code?: number, reason?: string): void;
on(event: "pong", listener: () => void): void;
on(event: "close", listener: () => void): void;
on(event: "error", listener: (err: Error) => void): void;
}
interface WsServer {
clients: Set<WsSocket>;
on(event: "connection", listener: (socket: WsSocket, req: IncomingMessage) => void): void;
on(event: "close", listener: () => void): void;
handleUpgrade(
req: IncomingMessage,
socket: Duplex,
head: Buffer,
callback: (ws: WsSocket) => void,
): void;
emit(event: "connection", ws: WsSocket, req: IncomingMessage): boolean;
}
const require = createRequire(import.meta.url);
const { WebSocket, WebSocketServer } = require("ws") as {
WebSocket: { OPEN: number };
WebSocketServer: new (opts: { noServer: boolean }) => WsServer;
};
interface UpgradeContext { interface UpgradeContext {
companyId: string; companyId: string;
actorType: "board" | "agent"; actorType: "board" | "agent";
@@ -154,8 +184,8 @@ export function setupLiveEventsWebSocketServer(
}, },
) { ) {
const wss = new WebSocketServer({ noServer: true }); const wss = new WebSocketServer({ noServer: true });
const cleanupByClient = new Map<WebSocket, () => void>(); const cleanupByClient = new Map<WsSocket, () => void>();
const aliveByClient = new Map<WebSocket, boolean>(); const aliveByClient = new Map<WsSocket, boolean>();
const pingInterval = setInterval(() => { const pingInterval = setInterval(() => {
for (const socket of wss.clients) { for (const socket of wss.clients) {
@@ -168,7 +198,7 @@ export function setupLiveEventsWebSocketServer(
} }
}, 30000); }, 30000);
wss.on("connection", (socket, req) => { wss.on("connection", (socket: WsSocket, req: IncomingMessage) => {
const context = (req as IncomingMessageWithContext).paperclipUpgradeContext; const context = (req as IncomingMessageWithContext).paperclipUpgradeContext;
if (!context) { if (!context) {
socket.close(1008, "missing context"); socket.close(1008, "missing context");
@@ -194,7 +224,7 @@ export function setupLiveEventsWebSocketServer(
aliveByClient.delete(socket); aliveByClient.delete(socket);
}); });
socket.on("error", (err) => { socket.on("error", (err: Error) => {
logger.warn({ err, companyId: context.companyId }, "live websocket client error"); logger.warn({ err, companyId: context.companyId }, "live websocket client error");
}); });
}); });
@@ -229,7 +259,7 @@ export function setupLiveEventsWebSocketServer(
const reqWithContext = req as IncomingMessageWithContext; const reqWithContext = req as IncomingMessageWithContext;
reqWithContext.paperclipUpgradeContext = context; reqWithContext.paperclipUpgradeContext = context;
wss.handleUpgrade(req, socket, head, (ws) => { wss.handleUpgrade(req, socket, head, (ws: WsSocket) => {
wss.emit("connection", ws, reqWithContext); wss.emit("connection", ws, reqWithContext);
}); });
}) })

View File

@@ -240,9 +240,9 @@ function FailedRunCard({
</span> </span>
)} )}
<div className="flex items-start justify-between gap-3"> <div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<div className="flex items-center gap-2"> <div className="flex flex-wrap items-center gap-2">
<span className="rounded-md bg-red-500/20 p-1.5"> <span className="rounded-md bg-red-500/20 p-1.5">
<XCircle className="h-4 w-4 text-red-600 dark:text-red-400" /> <XCircle className="h-4 w-4 text-red-600 dark:text-red-400" />
</span> </span>
@@ -257,12 +257,12 @@ function FailedRunCard({
{sourceLabel} run failed {timeAgo(run.createdAt)} {sourceLabel} run failed {timeAgo(run.createdAt)}
</p> </p>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex w-full flex-wrap items-center gap-2 sm:w-auto sm:justify-end">
<Button <Button
type="button" type="button"
variant="outline" variant="outline"
size="sm" size="sm"
className="h-8 px-2.5" className="h-8 shrink-0 px-2.5"
onClick={() => retryRun.mutate()} onClick={() => retryRun.mutate()}
disabled={retryRun.isPending} disabled={retryRun.isPending}
> >
@@ -273,7 +273,7 @@ function FailedRunCard({
type="button" type="button"
variant="outline" variant="outline"
size="sm" size="sm"
className="h-8 px-2.5" className="h-8 shrink-0 px-2.5"
asChild asChild
> >
<Link to={`/agents/${run.agentId}/runs/${run.id}`}> <Link to={`/agents/${run.agentId}/runs/${run.id}`}>