Merge branch 'codex/owner/ejclaw'
This commit is contained in:
@@ -1,15 +1,16 @@
|
||||
---
|
||||
name: setup
|
||||
description: Run initial EJClaw setup for the dual-service Discord architecture.
|
||||
description: Run initial EJClaw setup for the unified single-service Discord architecture.
|
||||
---
|
||||
|
||||
# EJClaw Setup
|
||||
|
||||
설치는 `bash setup.sh`로 부트스트랩하고, 나머지는 `bun run setup -- --step <name>`으로 진행합니다. 현재 기준 채널은 디스코드만 지원합니다.
|
||||
|
||||
EJClaw는 두 개의 서비스로 구성됩니다:
|
||||
- **ejclaw** — Claude Code 봇 (`@claude`)
|
||||
- **ejclaw-codex** — Codex 봇 (`@codex`) — `.env.codex` 파일이 있으면 자동 설치
|
||||
EJClaw는 현재 **단일 서비스**로 동작합니다:
|
||||
- **ejclaw** — 통합 런타임 서비스
|
||||
|
||||
Claude가 기본 봇으로 동작하고, Codex는 별도 서비스가 아니라 필요 시 내부 러너/역할 라우팅으로 사용됩니다.
|
||||
|
||||
## 1. 부트스트랩
|
||||
|
||||
@@ -34,7 +35,7 @@ bun run setup -- --step environment
|
||||
|
||||
## 3. 필수 환경 변수
|
||||
|
||||
### Claude 서비스 (.env)
|
||||
### 기본 환경 변수 (.env)
|
||||
|
||||
`.env`에 최소한 아래 값이 있어야 합니다.
|
||||
|
||||
@@ -51,18 +52,10 @@ CLAUDE_CODE_OAUTH_TOKENS=token1,token2 # 다중 계정 자동 로테이션
|
||||
GROQ_API_KEY=... # Discord 음성 전사 (Groq Whisper)
|
||||
```
|
||||
|
||||
### Codex 서비스 (.env.codex)
|
||||
|
||||
Codex 봇을 함께 운영하려면 `.env.codex`를 만듭니다. 이 파일이 있으면 `--step service`가 자동으로 `ejclaw-codex` 서비스도 설치합니다.
|
||||
Codex 관련 선택 설정은 **별도 서비스 파일이 아니라 같은 `.env` 또는 서비스 환경 변수**에 넣습니다:
|
||||
|
||||
```bash
|
||||
DISCORD_BOT_TOKEN=... # Codex 봇 토큰 (Claude와 별도)
|
||||
```
|
||||
|
||||
Codex 서비스의 추가 설정은 systemd 유닛에서 `Environment=` 라인으로 지정하거나 `.env.codex`에 추가합니다:
|
||||
|
||||
```bash
|
||||
# systemd 유닛 또는 .env.codex에 추가 가능
|
||||
# 같은 .env 또는 서비스 Environment=에 추가 가능
|
||||
CODEX_MODEL=gpt-5.4
|
||||
CODEX_EFFORT=xhigh
|
||||
OPENAI_API_KEY=...
|
||||
@@ -91,15 +84,17 @@ bun run setup -- --step runners
|
||||
- `runners/agent-runner` (Claude Code)
|
||||
- `runners/codex-runner` (Codex)
|
||||
|
||||
서비스는 하나지만, 내부 역할 라우팅과 paired 흐름에서 두 러너를 모두 쓸 수 있어서 둘 다 빌드합니다.
|
||||
|
||||
실패하면 보통 `bun run build:runners` 출력과 각 러너의 `package.json` 의존성을 같이 보면 됩니다.
|
||||
|
||||
## 5. 디스코드 채널 등록
|
||||
|
||||
먼저 디스코드에서 개발자 모드를 켜고 채널 ID를 복사합니다. 등록 JID는 `dc:<channel_id>` 형식입니다.
|
||||
|
||||
듀얼 서비스에서는 **같은 채널을 두 번 등록**할 수 있습니다 — 각 agent type에 한 번씩. 등록은 `(jid, agent_type)` 복합 키로 저장됩니다.
|
||||
채널 등록은 기본적으로 한 번 하면 됩니다.
|
||||
|
||||
Claude 봇 채널 등록:
|
||||
예시:
|
||||
|
||||
```bash
|
||||
bun run setup -- --step register -- \
|
||||
@@ -111,18 +106,6 @@ bun run setup -- --step register -- \
|
||||
--no-trigger-required
|
||||
```
|
||||
|
||||
Codex 봇도 같은 채널을 쓴다면 별도로 등록합니다:
|
||||
|
||||
```bash
|
||||
ASSISTANT_NAME=codex bun run setup -- --step register -- \
|
||||
--jid dc:123456789012345678 \
|
||||
--name "My Server #general" \
|
||||
--folder discord_main \
|
||||
--trigger @codex \
|
||||
--is-main \
|
||||
--no-trigger-required
|
||||
```
|
||||
|
||||
보조 채널 예시:
|
||||
|
||||
```bash
|
||||
@@ -140,24 +123,22 @@ bun run setup -- --step service
|
||||
```
|
||||
|
||||
이 명령은:
|
||||
- **ejclaw** 서비스를 항상 설치합니다
|
||||
- `.env.codex`가 있으면 **ejclaw-codex** 서비스도 함께 설치합니다
|
||||
- **ejclaw** 서비스를 설치하고 시작합니다
|
||||
|
||||
플랫폼별:
|
||||
- Linux (systemd): `~/.config/systemd/user/ejclaw.service` + `ejclaw-codex.service`
|
||||
- macOS: `~/Library/LaunchAgents/com.ejclaw.plist` + `com.ejclaw-codex.plist`
|
||||
- WSL (no systemd): `start-ejclaw.sh` + `start-ejclaw-codex.sh`
|
||||
- Linux (systemd): `~/.config/systemd/user/ejclaw.service`
|
||||
- macOS: `~/Library/LaunchAgents/com.ejclaw.plist`
|
||||
- WSL (no systemd): `start-ejclaw.sh`
|
||||
|
||||
수동으로 서비스 관리:
|
||||
|
||||
```bash
|
||||
# Linux (systemd)
|
||||
systemctl --user status ejclaw ejclaw-codex
|
||||
systemctl --user restart ejclaw ejclaw-codex
|
||||
systemctl --user status ejclaw
|
||||
systemctl --user restart ejclaw
|
||||
|
||||
# 로그
|
||||
journalctl --user -u ejclaw -f
|
||||
journalctl --user -u ejclaw-codex -f
|
||||
```
|
||||
|
||||
## 7. 최종 검증
|
||||
@@ -169,7 +150,6 @@ bun run setup -- --step verify
|
||||
성공 기준:
|
||||
|
||||
- **ejclaw** 서비스가 running
|
||||
- **ejclaw-codex** 서비스가 running (`.env.codex`가 있을 때)
|
||||
- Claude 인증이 configured
|
||||
- `CHANNEL_AUTH`에 `discord`
|
||||
- 등록 그룹 수가 1 이상
|
||||
@@ -178,6 +158,6 @@ bun run setup -- --step verify
|
||||
|
||||
- 빌드 문제: `bun run typecheck`, `bun test`, `bun run build:runners`
|
||||
- Claude 서비스 문제: `logs/ejclaw.error.log` 또는 `journalctl --user -u ejclaw -f`
|
||||
- Codex 서비스 문제: `logs/ejclaw-codex.error.log` 또는 `journalctl --user -u ejclaw-codex -f`
|
||||
- Codex 실행 문제: 같은 `ejclaw` 서비스 로그에서 `CODEX_MODEL`, `CODEX_EFFORT`, `OPENAI_API_KEY` 설정을 같이 확인
|
||||
- 디스코드 연결 문제: `.env`의 `DISCORD_BOT_TOKEN`과 등록된 `dc:*` JID 확인
|
||||
- 응답 문제: `tail -f logs/ejclaw.log`
|
||||
|
||||
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
@@ -3,7 +3,7 @@
|
||||
/groups/ @gavrielc @gabi-simons
|
||||
/launchd/ @gavrielc @gabi-simons
|
||||
/package.json @gavrielc @gabi-simons
|
||||
/package-lock.json @gavrielc @gabi-simons
|
||||
/bun.lock @gavrielc @gabi-simons
|
||||
|
||||
# Skills - open to contributors
|
||||
/.claude/skills/
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -48,7 +48,4 @@ runners/codex-runner-backups/
|
||||
.idea/
|
||||
.vscode/
|
||||
|
||||
# Skills system (local per-installation state)
|
||||
.nanoclaw/
|
||||
|
||||
agents-sdk-docs
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"allowedRoots": [
|
||||
{
|
||||
"path": "~/projects",
|
||||
"allowReadWrite": true,
|
||||
"description": "Development projects"
|
||||
},
|
||||
{
|
||||
"path": "~/repos",
|
||||
"allowReadWrite": true,
|
||||
"description": "Git repositories"
|
||||
},
|
||||
{
|
||||
"path": "~/Documents/work",
|
||||
"allowReadWrite": false,
|
||||
"description": "Work documents (read-only)"
|
||||
}
|
||||
],
|
||||
"blockedPatterns": [
|
||||
"password",
|
||||
"secret",
|
||||
"token"
|
||||
],
|
||||
"nonMainReadOnly": true
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
# Codex Assistant
|
||||
|
||||
You are a participant in a Discord chat. Your name is "코덱스" (Codex).
|
||||
|
||||
This channel works with the nanoclaw project at /home/clone-ej/nanoclaw.
|
||||
|
||||
## Core Rules
|
||||
|
||||
- Respond directly to messages. Do NOT give "reply suggestions" or "here's how you could respond".
|
||||
- When someone asks a question, answer it yourself. You are having a conversation, not helping someone draft replies.
|
||||
- Speak in natural, friendly Korean.
|
||||
- When coding, debugging, or file work is needed, do it directly.
|
||||
- When you reach a conclusion or need human judgment, mention @눈쟁이 in your message.
|
||||
@@ -1,38 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>com.ejclaw-codex</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>{{NODE_PATH}}</string>
|
||||
<string>{{PROJECT_ROOT}}/dist/index.js</string>
|
||||
</array>
|
||||
<key>WorkingDirectory</key>
|
||||
<string>{{PROJECT_ROOT}}</string>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
<key>KeepAlive</key>
|
||||
<true/>
|
||||
<key>EnvironmentVariables</key>
|
||||
<dict>
|
||||
<key>PATH</key>
|
||||
<string>{{HOME}}/.npm-global/bin:{{HOME}}/.local/bin:/usr/local/bin:/usr/bin:/bin</string>
|
||||
<key>HOME</key>
|
||||
<string>{{HOME}}</string>
|
||||
<key>ASSISTANT_NAME</key>
|
||||
<string>Andy-Codex</string>
|
||||
<key>EJCLAW_STORE_DIR</key>
|
||||
<string>{{PROJECT_ROOT}}/store-codex</string>
|
||||
<key>EJCLAW_GROUPS_DIR</key>
|
||||
<string>{{PROJECT_ROOT}}/groups-codex</string>
|
||||
<key>EJCLAW_DATA_DIR</key>
|
||||
<string>{{PROJECT_ROOT}}/data-codex</string>
|
||||
</dict>
|
||||
<key>StandardOutPath</key>
|
||||
<string>{{PROJECT_ROOT}}/logs/ejclaw-codex.log</string>
|
||||
<key>StandardErrorPath</key>
|
||||
<string>{{PROJECT_ROOT}}/logs/ejclaw-codex.error.log</string>
|
||||
</dict>
|
||||
</plist>
|
||||
3985
package-lock.json
generated
3985
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
2636
pnpm-lock.yaml
generated
2636
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
1524
runners/agent-runner/package-lock.json
generated
1524
runners/agent-runner/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -3,6 +3,7 @@
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"description": "Agent runner subprocess for EJClaw",
|
||||
"packageManager": "bun@1.3.11",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
|
||||
994
runners/agent-runner/pnpm-lock.yaml
generated
994
runners/agent-runner/pnpm-lock.yaml
generated
@@ -1,994 +0,0 @@
|
||||
lockfileVersion: '9.0'
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
'@anthropic-ai/claude-agent-sdk':
|
||||
specifier: ^0.2.81
|
||||
version: 0.2.81(zod@4.3.6)
|
||||
'@modelcontextprotocol/sdk':
|
||||
specifier: ^1.12.1
|
||||
version: 1.27.1(zod@4.3.6)
|
||||
cron-parser:
|
||||
specifier: ^5.0.0
|
||||
version: 5.5.0
|
||||
zod:
|
||||
specifier: ^4.0.0
|
||||
version: 4.3.6
|
||||
devDependencies:
|
||||
'@types/node':
|
||||
specifier: ^22.10.7
|
||||
version: 22.19.15
|
||||
typescript:
|
||||
specifier: ^5.7.3
|
||||
version: 5.9.3
|
||||
|
||||
packages:
|
||||
|
||||
'@anthropic-ai/claude-agent-sdk@0.2.81':
|
||||
resolution: {integrity: sha512-CBeebgibBEN/DWOQGZN67vhuTG55RbI1hlsFSSoZ4uA/Io3lw04eHTE2ISCmdbqyJaefYTt6GKZei1nP0TQMNw==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
zod: ^4.0.0
|
||||
|
||||
'@hono/node-server@1.19.11':
|
||||
resolution: {integrity: sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g==}
|
||||
engines: {node: '>=18.14.1'}
|
||||
peerDependencies:
|
||||
hono: ^4
|
||||
|
||||
'@img/sharp-darwin-arm64@0.34.5':
|
||||
resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@img/sharp-darwin-x64@0.34.5':
|
||||
resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@img/sharp-libvips-darwin-arm64@1.2.4':
|
||||
resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@img/sharp-libvips-darwin-x64@1.2.4':
|
||||
resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@img/sharp-libvips-linux-arm64@1.2.4':
|
||||
resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@img/sharp-libvips-linux-arm@1.2.4':
|
||||
resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@img/sharp-libvips-linux-x64@1.2.4':
|
||||
resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@img/sharp-libvips-linuxmusl-arm64@1.2.4':
|
||||
resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@img/sharp-libvips-linuxmusl-x64@1.2.4':
|
||||
resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@img/sharp-linux-arm64@0.34.5':
|
||||
resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@img/sharp-linux-arm@0.34.5':
|
||||
resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@img/sharp-linux-x64@0.34.5':
|
||||
resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@img/sharp-linuxmusl-arm64@0.34.5':
|
||||
resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@img/sharp-linuxmusl-x64@0.34.5':
|
||||
resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@img/sharp-win32-arm64@0.34.5':
|
||||
resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@img/sharp-win32-x64@0.34.5':
|
||||
resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@modelcontextprotocol/sdk@1.27.1':
|
||||
resolution: {integrity: sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
'@cfworker/json-schema': ^4.1.1
|
||||
zod: ^3.25 || ^4.0
|
||||
peerDependenciesMeta:
|
||||
'@cfworker/json-schema':
|
||||
optional: true
|
||||
|
||||
'@types/node@22.19.15':
|
||||
resolution: {integrity: sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==}
|
||||
|
||||
accepts@2.0.0:
|
||||
resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
ajv-formats@3.0.1:
|
||||
resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==}
|
||||
peerDependencies:
|
||||
ajv: ^8.0.0
|
||||
peerDependenciesMeta:
|
||||
ajv:
|
||||
optional: true
|
||||
|
||||
ajv@8.18.0:
|
||||
resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==}
|
||||
|
||||
body-parser@2.2.2:
|
||||
resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
bytes@3.1.2:
|
||||
resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
call-bind-apply-helpers@1.0.2:
|
||||
resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
call-bound@1.0.4:
|
||||
resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
content-disposition@1.0.1:
|
||||
resolution: {integrity: sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
content-type@1.0.5:
|
||||
resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
cookie-signature@1.2.2:
|
||||
resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==}
|
||||
engines: {node: '>=6.6.0'}
|
||||
|
||||
cookie@0.7.2:
|
||||
resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
cors@2.8.6:
|
||||
resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==}
|
||||
engines: {node: '>= 0.10'}
|
||||
|
||||
cron-parser@5.5.0:
|
||||
resolution: {integrity: sha512-oML4lKUXxizYswqmxuOCpgFS8BNUJpIu6k/2HVHyaL8Ynnf3wdf9tkns0yRdJLSIjkJ+b0DXHMZEHGpMwjnPww==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
debug@4.4.3:
|
||||
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
|
||||
engines: {node: '>=6.0'}
|
||||
peerDependencies:
|
||||
supports-color: '*'
|
||||
peerDependenciesMeta:
|
||||
supports-color:
|
||||
optional: true
|
||||
|
||||
depd@2.0.0:
|
||||
resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
dunder-proto@1.0.1:
|
||||
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
ee-first@1.1.1:
|
||||
resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
|
||||
|
||||
encodeurl@2.0.0:
|
||||
resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
es-define-property@1.0.1:
|
||||
resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
es-errors@1.3.0:
|
||||
resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
es-object-atoms@1.1.1:
|
||||
resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
escape-html@1.0.3:
|
||||
resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
|
||||
|
||||
etag@1.8.1:
|
||||
resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
eventsource-parser@3.0.6:
|
||||
resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
eventsource@3.0.7:
|
||||
resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
express-rate-limit@8.3.1:
|
||||
resolution: {integrity: sha512-D1dKN+cmyPWuvB+G2SREQDzPY1agpBIcTa9sJxOPMCNeH3gwzhqJRDWCXW3gg0y//+LQ/8j52JbMROWyrKdMdw==}
|
||||
engines: {node: '>= 16'}
|
||||
peerDependencies:
|
||||
express: '>= 4.11'
|
||||
|
||||
express@5.2.1:
|
||||
resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==}
|
||||
engines: {node: '>= 18'}
|
||||
|
||||
fast-deep-equal@3.1.3:
|
||||
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
|
||||
|
||||
fast-uri@3.1.0:
|
||||
resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==}
|
||||
|
||||
finalhandler@2.1.1:
|
||||
resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==}
|
||||
engines: {node: '>= 18.0.0'}
|
||||
|
||||
forwarded@0.2.0:
|
||||
resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
fresh@2.0.0:
|
||||
resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
function-bind@1.1.2:
|
||||
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
|
||||
|
||||
get-intrinsic@1.3.0:
|
||||
resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
get-proto@1.0.1:
|
||||
resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
gopd@1.2.0:
|
||||
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
has-symbols@1.1.0:
|
||||
resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
hasown@2.0.2:
|
||||
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
hono@4.12.8:
|
||||
resolution: {integrity: sha512-VJCEvtrezO1IAR+kqEYnxUOoStaQPGrCmX3j4wDTNOcD1uRPFpGlwQUIW8niPuvHXaTUxeOUl5MMDGrl+tmO9A==}
|
||||
engines: {node: '>=16.9.0'}
|
||||
|
||||
http-errors@2.0.1:
|
||||
resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
iconv-lite@0.7.2:
|
||||
resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
inherits@2.0.4:
|
||||
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
|
||||
|
||||
ip-address@10.1.0:
|
||||
resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==}
|
||||
engines: {node: '>= 12'}
|
||||
|
||||
ipaddr.js@1.9.1:
|
||||
resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
|
||||
engines: {node: '>= 0.10'}
|
||||
|
||||
is-promise@4.0.0:
|
||||
resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==}
|
||||
|
||||
isexe@2.0.0:
|
||||
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
|
||||
|
||||
jose@6.2.1:
|
||||
resolution: {integrity: sha512-jUaKr1yrbfaImV7R2TN/b3IcZzsw38/chqMpo2XJ7i2F8AfM/lA4G1goC3JVEwg0H7UldTmSt3P68nt31W7/mw==}
|
||||
|
||||
json-schema-traverse@1.0.0:
|
||||
resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
|
||||
|
||||
json-schema-typed@8.0.2:
|
||||
resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==}
|
||||
|
||||
luxon@3.7.2:
|
||||
resolution: {integrity: sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
math-intrinsics@1.1.0:
|
||||
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
media-typer@1.1.0:
|
||||
resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
merge-descriptors@2.0.0:
|
||||
resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
mime-db@1.54.0:
|
||||
resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
mime-types@3.0.2:
|
||||
resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
ms@2.1.3:
|
||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||
|
||||
negotiator@1.0.0:
|
||||
resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
object-assign@4.1.1:
|
||||
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
object-inspect@1.13.4:
|
||||
resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
on-finished@2.4.1:
|
||||
resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
once@1.4.0:
|
||||
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
||||
|
||||
parseurl@1.3.3:
|
||||
resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
path-key@3.1.1:
|
||||
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
path-to-regexp@8.3.0:
|
||||
resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==}
|
||||
|
||||
pkce-challenge@5.0.1:
|
||||
resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==}
|
||||
engines: {node: '>=16.20.0'}
|
||||
|
||||
proxy-addr@2.0.7:
|
||||
resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
|
||||
engines: {node: '>= 0.10'}
|
||||
|
||||
qs@6.15.0:
|
||||
resolution: {integrity: sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==}
|
||||
engines: {node: '>=0.6'}
|
||||
|
||||
range-parser@1.2.1:
|
||||
resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
raw-body@3.0.2:
|
||||
resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==}
|
||||
engines: {node: '>= 0.10'}
|
||||
|
||||
require-from-string@2.0.2:
|
||||
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
router@2.2.0:
|
||||
resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==}
|
||||
engines: {node: '>= 18'}
|
||||
|
||||
safer-buffer@2.1.2:
|
||||
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
|
||||
|
||||
send@1.2.1:
|
||||
resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==}
|
||||
engines: {node: '>= 18'}
|
||||
|
||||
serve-static@2.2.1:
|
||||
resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==}
|
||||
engines: {node: '>= 18'}
|
||||
|
||||
setprototypeof@1.2.0:
|
||||
resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
|
||||
|
||||
shebang-command@2.0.0:
|
||||
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
shebang-regex@3.0.0:
|
||||
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
side-channel-list@1.0.0:
|
||||
resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
side-channel-map@1.0.1:
|
||||
resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
side-channel-weakmap@1.0.2:
|
||||
resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
side-channel@1.1.0:
|
||||
resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
statuses@2.0.2:
|
||||
resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
toidentifier@1.0.1:
|
||||
resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
|
||||
engines: {node: '>=0.6'}
|
||||
|
||||
type-is@2.0.1:
|
||||
resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
typescript@5.9.3:
|
||||
resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
|
||||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
|
||||
undici-types@6.21.0:
|
||||
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
|
||||
|
||||
unpipe@1.0.0:
|
||||
resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
vary@1.1.2:
|
||||
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
which@2.0.2:
|
||||
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
|
||||
engines: {node: '>= 8'}
|
||||
hasBin: true
|
||||
|
||||
wrappy@1.0.2:
|
||||
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
|
||||
|
||||
zod-to-json-schema@3.25.1:
|
||||
resolution: {integrity: sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==}
|
||||
peerDependencies:
|
||||
zod: ^3.25 || ^4
|
||||
|
||||
zod@4.3.6:
|
||||
resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==}
|
||||
|
||||
snapshots:
|
||||
|
||||
'@anthropic-ai/claude-agent-sdk@0.2.81(zod@4.3.6)':
|
||||
dependencies:
|
||||
zod: 4.3.6
|
||||
optionalDependencies:
|
||||
'@img/sharp-darwin-arm64': 0.34.5
|
||||
'@img/sharp-darwin-x64': 0.34.5
|
||||
'@img/sharp-linux-arm': 0.34.5
|
||||
'@img/sharp-linux-arm64': 0.34.5
|
||||
'@img/sharp-linux-x64': 0.34.5
|
||||
'@img/sharp-linuxmusl-arm64': 0.34.5
|
||||
'@img/sharp-linuxmusl-x64': 0.34.5
|
||||
'@img/sharp-win32-arm64': 0.34.5
|
||||
'@img/sharp-win32-x64': 0.34.5
|
||||
|
||||
'@hono/node-server@1.19.11(hono@4.12.8)':
|
||||
dependencies:
|
||||
hono: 4.12.8
|
||||
|
||||
'@img/sharp-darwin-arm64@0.34.5':
|
||||
optionalDependencies:
|
||||
'@img/sharp-libvips-darwin-arm64': 1.2.4
|
||||
optional: true
|
||||
|
||||
'@img/sharp-darwin-x64@0.34.5':
|
||||
optionalDependencies:
|
||||
'@img/sharp-libvips-darwin-x64': 1.2.4
|
||||
optional: true
|
||||
|
||||
'@img/sharp-libvips-darwin-arm64@1.2.4':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-libvips-darwin-x64@1.2.4':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-libvips-linux-arm64@1.2.4':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-libvips-linux-arm@1.2.4':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-libvips-linux-x64@1.2.4':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-libvips-linuxmusl-arm64@1.2.4':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-libvips-linuxmusl-x64@1.2.4':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-linux-arm64@0.34.5':
|
||||
optionalDependencies:
|
||||
'@img/sharp-libvips-linux-arm64': 1.2.4
|
||||
optional: true
|
||||
|
||||
'@img/sharp-linux-arm@0.34.5':
|
||||
optionalDependencies:
|
||||
'@img/sharp-libvips-linux-arm': 1.2.4
|
||||
optional: true
|
||||
|
||||
'@img/sharp-linux-x64@0.34.5':
|
||||
optionalDependencies:
|
||||
'@img/sharp-libvips-linux-x64': 1.2.4
|
||||
optional: true
|
||||
|
||||
'@img/sharp-linuxmusl-arm64@0.34.5':
|
||||
optionalDependencies:
|
||||
'@img/sharp-libvips-linuxmusl-arm64': 1.2.4
|
||||
optional: true
|
||||
|
||||
'@img/sharp-linuxmusl-x64@0.34.5':
|
||||
optionalDependencies:
|
||||
'@img/sharp-libvips-linuxmusl-x64': 1.2.4
|
||||
optional: true
|
||||
|
||||
'@img/sharp-win32-arm64@0.34.5':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-win32-x64@0.34.5':
|
||||
optional: true
|
||||
|
||||
'@modelcontextprotocol/sdk@1.27.1(zod@4.3.6)':
|
||||
dependencies:
|
||||
'@hono/node-server': 1.19.11(hono@4.12.8)
|
||||
ajv: 8.18.0
|
||||
ajv-formats: 3.0.1(ajv@8.18.0)
|
||||
content-type: 1.0.5
|
||||
cors: 2.8.6
|
||||
cross-spawn: 7.0.6
|
||||
eventsource: 3.0.7
|
||||
eventsource-parser: 3.0.6
|
||||
express: 5.2.1
|
||||
express-rate-limit: 8.3.1(express@5.2.1)
|
||||
hono: 4.12.8
|
||||
jose: 6.2.1
|
||||
json-schema-typed: 8.0.2
|
||||
pkce-challenge: 5.0.1
|
||||
raw-body: 3.0.2
|
||||
zod: 4.3.6
|
||||
zod-to-json-schema: 3.25.1(zod@4.3.6)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@types/node@22.19.15':
|
||||
dependencies:
|
||||
undici-types: 6.21.0
|
||||
|
||||
accepts@2.0.0:
|
||||
dependencies:
|
||||
mime-types: 3.0.2
|
||||
negotiator: 1.0.0
|
||||
|
||||
ajv-formats@3.0.1(ajv@8.18.0):
|
||||
optionalDependencies:
|
||||
ajv: 8.18.0
|
||||
|
||||
ajv@8.18.0:
|
||||
dependencies:
|
||||
fast-deep-equal: 3.1.3
|
||||
fast-uri: 3.1.0
|
||||
json-schema-traverse: 1.0.0
|
||||
require-from-string: 2.0.2
|
||||
|
||||
body-parser@2.2.2:
|
||||
dependencies:
|
||||
bytes: 3.1.2
|
||||
content-type: 1.0.5
|
||||
debug: 4.4.3
|
||||
http-errors: 2.0.1
|
||||
iconv-lite: 0.7.2
|
||||
on-finished: 2.4.1
|
||||
qs: 6.15.0
|
||||
raw-body: 3.0.2
|
||||
type-is: 2.0.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
bytes@3.1.2: {}
|
||||
|
||||
call-bind-apply-helpers@1.0.2:
|
||||
dependencies:
|
||||
es-errors: 1.3.0
|
||||
function-bind: 1.1.2
|
||||
|
||||
call-bound@1.0.4:
|
||||
dependencies:
|
||||
call-bind-apply-helpers: 1.0.2
|
||||
get-intrinsic: 1.3.0
|
||||
|
||||
content-disposition@1.0.1: {}
|
||||
|
||||
content-type@1.0.5: {}
|
||||
|
||||
cookie-signature@1.2.2: {}
|
||||
|
||||
cookie@0.7.2: {}
|
||||
|
||||
cors@2.8.6:
|
||||
dependencies:
|
||||
object-assign: 4.1.1
|
||||
vary: 1.1.2
|
||||
|
||||
cron-parser@5.5.0:
|
||||
dependencies:
|
||||
luxon: 3.7.2
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
dependencies:
|
||||
path-key: 3.1.1
|
||||
shebang-command: 2.0.0
|
||||
which: 2.0.2
|
||||
|
||||
debug@4.4.3:
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
|
||||
depd@2.0.0: {}
|
||||
|
||||
dunder-proto@1.0.1:
|
||||
dependencies:
|
||||
call-bind-apply-helpers: 1.0.2
|
||||
es-errors: 1.3.0
|
||||
gopd: 1.2.0
|
||||
|
||||
ee-first@1.1.1: {}
|
||||
|
||||
encodeurl@2.0.0: {}
|
||||
|
||||
es-define-property@1.0.1: {}
|
||||
|
||||
es-errors@1.3.0: {}
|
||||
|
||||
es-object-atoms@1.1.1:
|
||||
dependencies:
|
||||
es-errors: 1.3.0
|
||||
|
||||
escape-html@1.0.3: {}
|
||||
|
||||
etag@1.8.1: {}
|
||||
|
||||
eventsource-parser@3.0.6: {}
|
||||
|
||||
eventsource@3.0.7:
|
||||
dependencies:
|
||||
eventsource-parser: 3.0.6
|
||||
|
||||
express-rate-limit@8.3.1(express@5.2.1):
|
||||
dependencies:
|
||||
express: 5.2.1
|
||||
ip-address: 10.1.0
|
||||
|
||||
express@5.2.1:
|
||||
dependencies:
|
||||
accepts: 2.0.0
|
||||
body-parser: 2.2.2
|
||||
content-disposition: 1.0.1
|
||||
content-type: 1.0.5
|
||||
cookie: 0.7.2
|
||||
cookie-signature: 1.2.2
|
||||
debug: 4.4.3
|
||||
depd: 2.0.0
|
||||
encodeurl: 2.0.0
|
||||
escape-html: 1.0.3
|
||||
etag: 1.8.1
|
||||
finalhandler: 2.1.1
|
||||
fresh: 2.0.0
|
||||
http-errors: 2.0.1
|
||||
merge-descriptors: 2.0.0
|
||||
mime-types: 3.0.2
|
||||
on-finished: 2.4.1
|
||||
once: 1.4.0
|
||||
parseurl: 1.3.3
|
||||
proxy-addr: 2.0.7
|
||||
qs: 6.15.0
|
||||
range-parser: 1.2.1
|
||||
router: 2.2.0
|
||||
send: 1.2.1
|
||||
serve-static: 2.2.1
|
||||
statuses: 2.0.2
|
||||
type-is: 2.0.1
|
||||
vary: 1.1.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
fast-deep-equal@3.1.3: {}
|
||||
|
||||
fast-uri@3.1.0: {}
|
||||
|
||||
finalhandler@2.1.1:
|
||||
dependencies:
|
||||
debug: 4.4.3
|
||||
encodeurl: 2.0.0
|
||||
escape-html: 1.0.3
|
||||
on-finished: 2.4.1
|
||||
parseurl: 1.3.3
|
||||
statuses: 2.0.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
forwarded@0.2.0: {}
|
||||
|
||||
fresh@2.0.0: {}
|
||||
|
||||
function-bind@1.1.2: {}
|
||||
|
||||
get-intrinsic@1.3.0:
|
||||
dependencies:
|
||||
call-bind-apply-helpers: 1.0.2
|
||||
es-define-property: 1.0.1
|
||||
es-errors: 1.3.0
|
||||
es-object-atoms: 1.1.1
|
||||
function-bind: 1.1.2
|
||||
get-proto: 1.0.1
|
||||
gopd: 1.2.0
|
||||
has-symbols: 1.1.0
|
||||
hasown: 2.0.2
|
||||
math-intrinsics: 1.1.0
|
||||
|
||||
get-proto@1.0.1:
|
||||
dependencies:
|
||||
dunder-proto: 1.0.1
|
||||
es-object-atoms: 1.1.1
|
||||
|
||||
gopd@1.2.0: {}
|
||||
|
||||
has-symbols@1.1.0: {}
|
||||
|
||||
hasown@2.0.2:
|
||||
dependencies:
|
||||
function-bind: 1.1.2
|
||||
|
||||
hono@4.12.8: {}
|
||||
|
||||
http-errors@2.0.1:
|
||||
dependencies:
|
||||
depd: 2.0.0
|
||||
inherits: 2.0.4
|
||||
setprototypeof: 1.2.0
|
||||
statuses: 2.0.2
|
||||
toidentifier: 1.0.1
|
||||
|
||||
iconv-lite@0.7.2:
|
||||
dependencies:
|
||||
safer-buffer: 2.1.2
|
||||
|
||||
inherits@2.0.4: {}
|
||||
|
||||
ip-address@10.1.0: {}
|
||||
|
||||
ipaddr.js@1.9.1: {}
|
||||
|
||||
is-promise@4.0.0: {}
|
||||
|
||||
isexe@2.0.0: {}
|
||||
|
||||
jose@6.2.1: {}
|
||||
|
||||
json-schema-traverse@1.0.0: {}
|
||||
|
||||
json-schema-typed@8.0.2: {}
|
||||
|
||||
luxon@3.7.2: {}
|
||||
|
||||
math-intrinsics@1.1.0: {}
|
||||
|
||||
media-typer@1.1.0: {}
|
||||
|
||||
merge-descriptors@2.0.0: {}
|
||||
|
||||
mime-db@1.54.0: {}
|
||||
|
||||
mime-types@3.0.2:
|
||||
dependencies:
|
||||
mime-db: 1.54.0
|
||||
|
||||
ms@2.1.3: {}
|
||||
|
||||
negotiator@1.0.0: {}
|
||||
|
||||
object-assign@4.1.1: {}
|
||||
|
||||
object-inspect@1.13.4: {}
|
||||
|
||||
on-finished@2.4.1:
|
||||
dependencies:
|
||||
ee-first: 1.1.1
|
||||
|
||||
once@1.4.0:
|
||||
dependencies:
|
||||
wrappy: 1.0.2
|
||||
|
||||
parseurl@1.3.3: {}
|
||||
|
||||
path-key@3.1.1: {}
|
||||
|
||||
path-to-regexp@8.3.0: {}
|
||||
|
||||
pkce-challenge@5.0.1: {}
|
||||
|
||||
proxy-addr@2.0.7:
|
||||
dependencies:
|
||||
forwarded: 0.2.0
|
||||
ipaddr.js: 1.9.1
|
||||
|
||||
qs@6.15.0:
|
||||
dependencies:
|
||||
side-channel: 1.1.0
|
||||
|
||||
range-parser@1.2.1: {}
|
||||
|
||||
raw-body@3.0.2:
|
||||
dependencies:
|
||||
bytes: 3.1.2
|
||||
http-errors: 2.0.1
|
||||
iconv-lite: 0.7.2
|
||||
unpipe: 1.0.0
|
||||
|
||||
require-from-string@2.0.2: {}
|
||||
|
||||
router@2.2.0:
|
||||
dependencies:
|
||||
debug: 4.4.3
|
||||
depd: 2.0.0
|
||||
is-promise: 4.0.0
|
||||
parseurl: 1.3.3
|
||||
path-to-regexp: 8.3.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
safer-buffer@2.1.2: {}
|
||||
|
||||
send@1.2.1:
|
||||
dependencies:
|
||||
debug: 4.4.3
|
||||
encodeurl: 2.0.0
|
||||
escape-html: 1.0.3
|
||||
etag: 1.8.1
|
||||
fresh: 2.0.0
|
||||
http-errors: 2.0.1
|
||||
mime-types: 3.0.2
|
||||
ms: 2.1.3
|
||||
on-finished: 2.4.1
|
||||
range-parser: 1.2.1
|
||||
statuses: 2.0.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
serve-static@2.2.1:
|
||||
dependencies:
|
||||
encodeurl: 2.0.0
|
||||
escape-html: 1.0.3
|
||||
parseurl: 1.3.3
|
||||
send: 1.2.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
setprototypeof@1.2.0: {}
|
||||
|
||||
shebang-command@2.0.0:
|
||||
dependencies:
|
||||
shebang-regex: 3.0.0
|
||||
|
||||
shebang-regex@3.0.0: {}
|
||||
|
||||
side-channel-list@1.0.0:
|
||||
dependencies:
|
||||
es-errors: 1.3.0
|
||||
object-inspect: 1.13.4
|
||||
|
||||
side-channel-map@1.0.1:
|
||||
dependencies:
|
||||
call-bound: 1.0.4
|
||||
es-errors: 1.3.0
|
||||
get-intrinsic: 1.3.0
|
||||
object-inspect: 1.13.4
|
||||
|
||||
side-channel-weakmap@1.0.2:
|
||||
dependencies:
|
||||
call-bound: 1.0.4
|
||||
es-errors: 1.3.0
|
||||
get-intrinsic: 1.3.0
|
||||
object-inspect: 1.13.4
|
||||
side-channel-map: 1.0.1
|
||||
|
||||
side-channel@1.1.0:
|
||||
dependencies:
|
||||
es-errors: 1.3.0
|
||||
object-inspect: 1.13.4
|
||||
side-channel-list: 1.0.0
|
||||
side-channel-map: 1.0.1
|
||||
side-channel-weakmap: 1.0.2
|
||||
|
||||
statuses@2.0.2: {}
|
||||
|
||||
toidentifier@1.0.1: {}
|
||||
|
||||
type-is@2.0.1:
|
||||
dependencies:
|
||||
content-type: 1.0.5
|
||||
media-typer: 1.1.0
|
||||
mime-types: 3.0.2
|
||||
|
||||
typescript@5.9.3: {}
|
||||
|
||||
undici-types@6.21.0: {}
|
||||
|
||||
unpipe@1.0.0: {}
|
||||
|
||||
vary@1.1.2: {}
|
||||
|
||||
which@2.0.2:
|
||||
dependencies:
|
||||
isexe: 2.0.0
|
||||
|
||||
wrappy@1.0.2: {}
|
||||
|
||||
zod-to-json-schema@3.25.1(zod@4.3.6):
|
||||
dependencies:
|
||||
zod: 4.3.6
|
||||
|
||||
zod@4.3.6: {}
|
||||
@@ -137,9 +137,6 @@ function createExecutableWrapperDir(baseEnv: NodeJS.ProcessEnv): string {
|
||||
const candidateRoots = [
|
||||
baseEnv.EJCLAW_REVIEWER_GIT_WRAPPER_ROOT,
|
||||
baseEnv.HOME ? path.join(baseEnv.HOME, '.ejclaw-reviewer-runtime') : null,
|
||||
process.cwd()
|
||||
? path.join(process.cwd(), '.ejclaw-reviewer-runtime')
|
||||
: null,
|
||||
path.join(os.tmpdir(), '.ejclaw-reviewer-runtime'),
|
||||
].filter((value): value is string => Boolean(value));
|
||||
|
||||
|
||||
@@ -175,7 +175,7 @@ describe('claude reviewer runtime guard', () => {
|
||||
|
||||
it('prefers an executable HOME-scoped wrapper dir before tmp', () => {
|
||||
const homeDir = fs.mkdtempSync(
|
||||
path.join(process.cwd(), '.ejclaw-reviewer-home-'),
|
||||
path.join(os.tmpdir(), 'ejclaw-reviewer-home-'),
|
||||
);
|
||||
try {
|
||||
const env = buildReviewerGitGuardEnv(
|
||||
|
||||
172
runners/codex-runner/package-lock.json
generated
172
runners/codex-runner/package-lock.json
generated
@@ -1,172 +0,0 @@
|
||||
{
|
||||
"name": "ejclaw-codex-runner",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "ejclaw-codex-runner",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@openai/codex": "^0.115.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.10.7",
|
||||
"typescript": "^5.7.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@openai/codex": {
|
||||
"version": "0.115.0",
|
||||
"resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.115.0.tgz",
|
||||
"integrity": "sha512-uu689DHUzvuPcb39hJ+7fqy++TAvY32w9VggDpcz3HS0Sx0WadWoAPPcMK547P2T6AqfMsAtA8kspkR3tqErOg==",
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"codex": "bin/codex.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@openai/codex-darwin-arm64": "npm:@openai/codex@0.115.0-darwin-arm64",
|
||||
"@openai/codex-darwin-x64": "npm:@openai/codex@0.115.0-darwin-x64",
|
||||
"@openai/codex-linux-arm64": "npm:@openai/codex@0.115.0-linux-arm64",
|
||||
"@openai/codex-linux-x64": "npm:@openai/codex@0.115.0-linux-x64",
|
||||
"@openai/codex-win32-arm64": "npm:@openai/codex@0.115.0-win32-arm64",
|
||||
"@openai/codex-win32-x64": "npm:@openai/codex@0.115.0-win32-x64"
|
||||
}
|
||||
},
|
||||
"node_modules/@openai/codex-darwin-arm64": {
|
||||
"name": "@openai/codex",
|
||||
"version": "0.115.0-darwin-arm64",
|
||||
"resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.115.0-darwin-arm64.tgz",
|
||||
"integrity": "sha512-wTWV+YlDTL0y0mL+FMmbzhilm+dPkbIZTe/lH3LwBzcEMhgSGLsPdZLfAiMND4wFE21HS7H0pjMogNQEMLQmqg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/@openai/codex-darwin-x64": {
|
||||
"name": "@openai/codex",
|
||||
"version": "0.115.0-darwin-x64",
|
||||
"resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.115.0-darwin-x64.tgz",
|
||||
"integrity": "sha512-EPzgymU4CFp83qjv29wXFwhWib3zC8g6SLTJGh2OpcJiOYyLY0bO53FB+QchL1jC9gm1uLyD5j5F3ddI0AbjIg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/@openai/codex-linux-arm64": {
|
||||
"name": "@openai/codex",
|
||||
"version": "0.115.0-linux-arm64",
|
||||
"resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.115.0-linux-arm64.tgz",
|
||||
"integrity": "sha512-40+SCyI+LvVx/iX30qH7dTQzWt9MZxDaK2E6YRB4hoYej5UALrhkFNzweHa5uvqvhmqGZCuagZOYB8285eGCgw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/@openai/codex-linux-x64": {
|
||||
"name": "@openai/codex",
|
||||
"version": "0.115.0-linux-x64",
|
||||
"resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.115.0-linux-x64.tgz",
|
||||
"integrity": "sha512-+6eRd2p4KMrhQvuF7XpjYIdCA2Ok2LbhOq+ywZdLpHbmwQ/Yynd0gJ/Q90xCeo2vloCwl9WsbsiVVSahG5FyWg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/@openai/codex-win32-arm64": {
|
||||
"name": "@openai/codex",
|
||||
"version": "0.115.0-win32-arm64",
|
||||
"resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.115.0-win32-arm64.tgz",
|
||||
"integrity": "sha512-yaYhQ0kPL9Kithmrr1vh5A4c+gqAMhMZeA/htTtkpWW8RQkmwgcYYpX/Ky2cEzu0w51pvxQQy63LgHftc+pg1Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/@openai/codex-win32-x64": {
|
||||
"name": "@openai/codex",
|
||||
"version": "0.115.0-win32-x64",
|
||||
"resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.115.0-win32-x64.tgz",
|
||||
"integrity": "sha512-wtYf2HJCB+p+Uj7o1W08fRgZMzKCVGRQ4YdSfLRNfF4wwPqX5XsK9blsv7brukn5J9lclYxagiR6qGvbfHbD7w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "22.19.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.15.tgz",
|
||||
"integrity": "sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.21.0"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "6.21.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"description": "Codex app-server runner for EJClaw",
|
||||
"packageManager": "bun@1.3.11",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
|
||||
111
runners/codex-runner/pnpm-lock.yaml
generated
111
runners/codex-runner/pnpm-lock.yaml
generated
@@ -1,111 +0,0 @@
|
||||
lockfileVersion: '9.0'
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
'@openai/codex':
|
||||
specifier: ^0.115.0
|
||||
version: 0.115.0
|
||||
devDependencies:
|
||||
'@types/node':
|
||||
specifier: ^22.10.7
|
||||
version: 22.19.15
|
||||
typescript:
|
||||
specifier: ^5.7.3
|
||||
version: 5.9.3
|
||||
|
||||
packages:
|
||||
|
||||
'@openai/codex@0.115.0':
|
||||
resolution: {integrity: sha512-uu689DHUzvuPcb39hJ+7fqy++TAvY32w9VggDpcz3HS0Sx0WadWoAPPcMK547P2T6AqfMsAtA8kspkR3tqErOg==}
|
||||
engines: {node: '>=16'}
|
||||
hasBin: true
|
||||
|
||||
'@openai/codex@0.115.0-darwin-arm64':
|
||||
resolution: {integrity: sha512-wTWV+YlDTL0y0mL+FMmbzhilm+dPkbIZTe/lH3LwBzcEMhgSGLsPdZLfAiMND4wFE21HS7H0pjMogNQEMLQmqg==}
|
||||
engines: {node: '>=16'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@openai/codex@0.115.0-darwin-x64':
|
||||
resolution: {integrity: sha512-EPzgymU4CFp83qjv29wXFwhWib3zC8g6SLTJGh2OpcJiOYyLY0bO53FB+QchL1jC9gm1uLyD5j5F3ddI0AbjIg==}
|
||||
engines: {node: '>=16'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@openai/codex@0.115.0-linux-arm64':
|
||||
resolution: {integrity: sha512-40+SCyI+LvVx/iX30qH7dTQzWt9MZxDaK2E6YRB4hoYej5UALrhkFNzweHa5uvqvhmqGZCuagZOYB8285eGCgw==}
|
||||
engines: {node: '>=16'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@openai/codex@0.115.0-linux-x64':
|
||||
resolution: {integrity: sha512-+6eRd2p4KMrhQvuF7XpjYIdCA2Ok2LbhOq+ywZdLpHbmwQ/Yynd0gJ/Q90xCeo2vloCwl9WsbsiVVSahG5FyWg==}
|
||||
engines: {node: '>=16'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@openai/codex@0.115.0-win32-arm64':
|
||||
resolution: {integrity: sha512-yaYhQ0kPL9Kithmrr1vh5A4c+gqAMhMZeA/htTtkpWW8RQkmwgcYYpX/Ky2cEzu0w51pvxQQy63LgHftc+pg1Q==}
|
||||
engines: {node: '>=16'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@openai/codex@0.115.0-win32-x64':
|
||||
resolution: {integrity: sha512-wtYf2HJCB+p+Uj7o1W08fRgZMzKCVGRQ4YdSfLRNfF4wwPqX5XsK9blsv7brukn5J9lclYxagiR6qGvbfHbD7w==}
|
||||
engines: {node: '>=16'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@types/node@22.19.15':
|
||||
resolution: {integrity: sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==}
|
||||
|
||||
typescript@5.9.3:
|
||||
resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
|
||||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
|
||||
undici-types@6.21.0:
|
||||
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
|
||||
|
||||
snapshots:
|
||||
|
||||
'@openai/codex@0.115.0':
|
||||
optionalDependencies:
|
||||
'@openai/codex-darwin-arm64': '@openai/codex@0.115.0-darwin-arm64'
|
||||
'@openai/codex-darwin-x64': '@openai/codex@0.115.0-darwin-x64'
|
||||
'@openai/codex-linux-arm64': '@openai/codex@0.115.0-linux-arm64'
|
||||
'@openai/codex-linux-x64': '@openai/codex@0.115.0-linux-x64'
|
||||
'@openai/codex-win32-arm64': '@openai/codex@0.115.0-win32-arm64'
|
||||
'@openai/codex-win32-x64': '@openai/codex@0.115.0-win32-x64'
|
||||
|
||||
'@openai/codex@0.115.0-darwin-arm64':
|
||||
optional: true
|
||||
|
||||
'@openai/codex@0.115.0-darwin-x64':
|
||||
optional: true
|
||||
|
||||
'@openai/codex@0.115.0-linux-arm64':
|
||||
optional: true
|
||||
|
||||
'@openai/codex@0.115.0-linux-x64':
|
||||
optional: true
|
||||
|
||||
'@openai/codex@0.115.0-win32-arm64':
|
||||
optional: true
|
||||
|
||||
'@openai/codex@0.115.0-win32-x64':
|
||||
optional: true
|
||||
|
||||
'@types/node@22.19.15':
|
||||
dependencies:
|
||||
undici-types: 6.21.0
|
||||
|
||||
typescript@5.9.3: {}
|
||||
|
||||
undici-types@6.21.0: {}
|
||||
@@ -49,9 +49,6 @@ function createExecutableWrapperDir(baseEnv: NodeJS.ProcessEnv): string {
|
||||
const candidateRoots = [
|
||||
baseEnv.EJCLAW_REVIEWER_GIT_WRAPPER_ROOT,
|
||||
baseEnv.HOME ? path.join(baseEnv.HOME, '.ejclaw-reviewer-runtime') : null,
|
||||
process.cwd()
|
||||
? path.join(process.cwd(), '.ejclaw-reviewer-runtime')
|
||||
: null,
|
||||
path.join(os.tmpdir(), '.ejclaw-reviewer-runtime'),
|
||||
].filter((value): value is string => Boolean(value));
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ describe('codex reviewer runtime guard', () => {
|
||||
|
||||
it('prefers an executable HOME-scoped wrapper dir before tmp', () => {
|
||||
const homeDir = fs.mkdtempSync(
|
||||
path.join(process.cwd(), '.ejclaw-reviewer-home-'),
|
||||
path.join(os.tmpdir(), 'ejclaw-reviewer-home-'),
|
||||
);
|
||||
try {
|
||||
const env = buildReviewerGitGuardEnv(
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
import fs from 'fs';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
const { execSyncMock } = vi.hoisted(() => ({
|
||||
execSyncMock: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('child_process', () => ({
|
||||
execSync: execSyncMock,
|
||||
}));
|
||||
|
||||
import {
|
||||
detectLegacyServiceIssues,
|
||||
formatLegacyServiceFailureMessage,
|
||||
} from './legacy-service-guard.js';
|
||||
|
||||
describe('legacy service guard', () => {
|
||||
const tempRoots: string[] = [];
|
||||
|
||||
afterEach(() => {
|
||||
execSyncMock.mockReset();
|
||||
for (const root of tempRoots.splice(0)) {
|
||||
fs.rmSync(root, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it('detects legacy systemd units in the opposite scope', () => {
|
||||
const projectRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ejclaw-legacy-'));
|
||||
tempRoots.push(projectRoot);
|
||||
|
||||
execSyncMock.mockImplementation((cmd: string) => {
|
||||
if (cmd === 'systemctl is-active ejclaw-codex') {
|
||||
throw new Error('inactive');
|
||||
}
|
||||
if (cmd === 'systemctl list-unit-files') {
|
||||
return 'ejclaw-codex.service enabled\n';
|
||||
}
|
||||
if (cmd === 'systemctl --user is-active ejclaw-codex') {
|
||||
throw new Error('inactive');
|
||||
}
|
||||
if (cmd === 'systemctl --user list-unit-files') {
|
||||
return '';
|
||||
}
|
||||
if (cmd === 'systemctl is-active ejclaw-review') {
|
||||
throw new Error('inactive');
|
||||
}
|
||||
if (cmd === 'systemctl --user is-active ejclaw-review') {
|
||||
throw new Error('inactive');
|
||||
}
|
||||
if (cmd === 'systemctl list-unit-files' || cmd === 'systemctl --user list-unit-files') {
|
||||
return '';
|
||||
}
|
||||
throw new Error(`unexpected command: ${cmd}`);
|
||||
});
|
||||
|
||||
expect(detectLegacyServiceIssues(projectRoot, 'systemd')).toEqual([
|
||||
{
|
||||
name: 'ejclaw-codex',
|
||||
status: 'stopped',
|
||||
sources: ['systemd-system'],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('detects legacy nohup artifacts even on systemd hosts', () => {
|
||||
const projectRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ejclaw-legacy-'));
|
||||
tempRoots.push(projectRoot);
|
||||
fs.writeFileSync(path.join(projectRoot, 'start-ejclaw-codex.sh'), '#!/bin/bash\n');
|
||||
|
||||
execSyncMock.mockImplementation((cmd: string) => {
|
||||
if (
|
||||
cmd === 'systemctl is-active ejclaw-codex' ||
|
||||
cmd === 'systemctl --user is-active ejclaw-codex' ||
|
||||
cmd === 'systemctl is-active ejclaw-review' ||
|
||||
cmd === 'systemctl --user is-active ejclaw-review'
|
||||
) {
|
||||
throw new Error('inactive');
|
||||
}
|
||||
if (
|
||||
cmd === 'systemctl list-unit-files' ||
|
||||
cmd === 'systemctl --user list-unit-files'
|
||||
) {
|
||||
return '';
|
||||
}
|
||||
throw new Error(`unexpected command: ${cmd}`);
|
||||
});
|
||||
|
||||
expect(detectLegacyServiceIssues(projectRoot, 'systemd')).toEqual([
|
||||
{
|
||||
name: 'ejclaw-codex',
|
||||
status: 'stopped',
|
||||
sources: ['nohup'],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('formats cleanup instructions for the detected scopes only', () => {
|
||||
const message = formatLegacyServiceFailureMessage({
|
||||
projectRoot: '/srv/ejclaw',
|
||||
serviceManager: 'systemd',
|
||||
homeDir: '/home/user',
|
||||
services: [
|
||||
{
|
||||
name: 'ejclaw-codex',
|
||||
status: 'stopped',
|
||||
sources: ['systemd-system', 'nohup'],
|
||||
},
|
||||
{
|
||||
name: 'ejclaw-review',
|
||||
status: 'running',
|
||||
sources: ['systemd-user'],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(message).toContain('systemctl disable --now ejclaw-codex');
|
||||
expect(message).toContain('systemctl --user disable --now ejclaw-review');
|
||||
expect(message).toContain('pkill -F "/srv/ejclaw/ejclaw-codex.pid"');
|
||||
expect(message).not.toContain('systemctl --user disable --now ejclaw-codex');
|
||||
});
|
||||
});
|
||||
@@ -1,209 +0,0 @@
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
|
||||
import type { ServiceManager } from './platform.js';
|
||||
import { getLegacyServiceDefs } from './service-defs.js';
|
||||
import {
|
||||
checkLaunchdServiceArtifact,
|
||||
checkNohupServiceArtifact,
|
||||
checkSystemdServiceInScope,
|
||||
type ServiceCheck,
|
||||
type SystemdScope,
|
||||
} from './verify-services.js';
|
||||
|
||||
type LegacySource = 'launchd' | 'systemd-system' | 'systemd-user' | 'nohup';
|
||||
|
||||
export interface LegacyServiceIssue extends ServiceCheck {
|
||||
sources: LegacySource[];
|
||||
}
|
||||
|
||||
function summarizeLegacyStatus(statuses: ServiceCheck['status'][]): ServiceCheck['status'] {
|
||||
if (statuses.includes('running')) {
|
||||
return 'running';
|
||||
}
|
||||
if (statuses.includes('stopped')) {
|
||||
return 'stopped';
|
||||
}
|
||||
return 'not_found';
|
||||
}
|
||||
|
||||
export function detectLegacyServiceIssues(
|
||||
projectRoot: string,
|
||||
serviceManager: ServiceManager,
|
||||
homeDir = os.homedir(),
|
||||
): LegacyServiceIssue[] {
|
||||
return getLegacyServiceDefs(projectRoot)
|
||||
.map((def) => {
|
||||
if (serviceManager === 'launchd') {
|
||||
const status = checkLaunchdServiceArtifact(
|
||||
def.launchdLabel,
|
||||
path.join(homeDir, 'Library', 'LaunchAgents', `${def.launchdLabel}.plist`),
|
||||
);
|
||||
if (status === 'not_found') return null;
|
||||
return {
|
||||
name: def.name,
|
||||
status,
|
||||
sources: ['launchd'] as LegacySource[],
|
||||
};
|
||||
}
|
||||
|
||||
if (serviceManager === 'systemd') {
|
||||
const scopedChecks: Array<{ source: LegacySource; status: ServiceCheck['status'] }> = [
|
||||
{
|
||||
source: 'systemd-system',
|
||||
status: checkSystemdServiceInScope(def.name, 'system'),
|
||||
},
|
||||
{
|
||||
source: 'systemd-user',
|
||||
status: checkSystemdServiceInScope(def.name, 'user'),
|
||||
},
|
||||
{
|
||||
source: 'nohup',
|
||||
status: checkNohupServiceArtifact(projectRoot, def.name),
|
||||
},
|
||||
];
|
||||
const detected = scopedChecks.filter(
|
||||
(current) => current.status !== 'not_found',
|
||||
);
|
||||
if (detected.length === 0) return null;
|
||||
return {
|
||||
name: def.name,
|
||||
status: summarizeLegacyStatus(detected.map((current) => current.status)),
|
||||
sources: detected.map((current) => current.source),
|
||||
};
|
||||
}
|
||||
|
||||
const status = checkNohupServiceArtifact(projectRoot, def.name);
|
||||
if (status === 'not_found') return null;
|
||||
return {
|
||||
name: def.name,
|
||||
status,
|
||||
sources: ['nohup'] as LegacySource[],
|
||||
};
|
||||
})
|
||||
.filter((service): service is LegacyServiceIssue => Boolean(service));
|
||||
}
|
||||
|
||||
function formatSystemdCleanupForScope(
|
||||
serviceNames: string[],
|
||||
homeDir: string,
|
||||
scope: SystemdScope,
|
||||
): string[] {
|
||||
if (serviceNames.length === 0) return [];
|
||||
const systemctlPrefix = scope === 'system' ? 'systemctl' : 'systemctl --user';
|
||||
const unitDir =
|
||||
scope === 'system'
|
||||
? '/etc/systemd/system'
|
||||
: path.join(homeDir, '.config', 'systemd', 'user');
|
||||
const unitPaths = serviceNames
|
||||
.map((serviceName) =>
|
||||
JSON.stringify(path.join(unitDir, `${serviceName}.service`)),
|
||||
)
|
||||
.join(' ');
|
||||
|
||||
return [
|
||||
`${systemctlPrefix} disable --now ${serviceNames.join(' ')}`,
|
||||
`rm -f ${unitPaths}`,
|
||||
`${systemctlPrefix} daemon-reload`,
|
||||
];
|
||||
}
|
||||
|
||||
function formatLaunchdCleanup(serviceNames: string[], homeDir: string): string {
|
||||
if (serviceNames.length === 0) {
|
||||
return '';
|
||||
}
|
||||
const plistPaths = serviceNames.map((serviceName) => {
|
||||
const label = serviceName === 'ejclaw-codex'
|
||||
? 'com.ejclaw-codex'
|
||||
: 'com.ejclaw-review';
|
||||
return path.join(homeDir, 'Library', 'LaunchAgents', `${label}.plist`);
|
||||
});
|
||||
|
||||
return [
|
||||
...plistPaths.map(
|
||||
(plistPath) =>
|
||||
`launchctl unload ${JSON.stringify(plistPath)} 2>/dev/null || true`,
|
||||
),
|
||||
`rm -f ${plistPaths.map((plistPath) => JSON.stringify(plistPath)).join(' ')}`,
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
function formatNohupCleanup(
|
||||
projectRoot: string,
|
||||
serviceNames: string[],
|
||||
): string {
|
||||
if (serviceNames.length === 0) {
|
||||
return '';
|
||||
}
|
||||
const pidPaths = serviceNames
|
||||
.map((serviceName) => path.join(projectRoot, `${serviceName}.pid`))
|
||||
.map((currentPath) => JSON.stringify(currentPath));
|
||||
const wrapperPaths = serviceNames
|
||||
.map((serviceName) => path.join(projectRoot, `start-${serviceName}.sh`))
|
||||
.map((currentPath) => JSON.stringify(currentPath));
|
||||
|
||||
return [
|
||||
...pidPaths.map((pidPath) => `pkill -F ${pidPath} 2>/dev/null || true`),
|
||||
`rm -f ${[...pidPaths, ...wrapperPaths].join(' ')}`,
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
export function formatLegacyServiceFailureMessage(args: {
|
||||
projectRoot: string;
|
||||
serviceManager: ServiceManager;
|
||||
services: LegacyServiceIssue[];
|
||||
homeDir?: string;
|
||||
}): string {
|
||||
const homeDir = args.homeDir ?? os.homedir();
|
||||
const details = args.services
|
||||
.map(
|
||||
(service) =>
|
||||
`${service.name}=${service.status} [${service.sources.join(',')}]`,
|
||||
)
|
||||
.join(', ');
|
||||
|
||||
let cleanupLines: string[] = [];
|
||||
if (args.serviceManager === 'launchd') {
|
||||
cleanupLines = formatLaunchdCleanup(
|
||||
args.services
|
||||
.filter((service) => service.sources.includes('launchd'))
|
||||
.map((service) => service.name),
|
||||
homeDir,
|
||||
).split('\n');
|
||||
} else if (args.serviceManager === 'systemd') {
|
||||
cleanupLines = [
|
||||
...formatSystemdCleanupForScope(
|
||||
args.services
|
||||
.filter((service) => service.sources.includes('systemd-system'))
|
||||
.map((service) => service.name),
|
||||
homeDir,
|
||||
'system',
|
||||
),
|
||||
...formatSystemdCleanupForScope(
|
||||
args.services
|
||||
.filter((service) => service.sources.includes('systemd-user'))
|
||||
.map((service) => service.name),
|
||||
homeDir,
|
||||
'user',
|
||||
),
|
||||
...formatNohupCleanup(
|
||||
args.projectRoot,
|
||||
args.services
|
||||
.filter((service) => service.sources.includes('nohup'))
|
||||
.map((service) => service.name),
|
||||
).split('\n'),
|
||||
].filter(Boolean);
|
||||
} else {
|
||||
cleanupLines = formatNohupCleanup(
|
||||
args.projectRoot,
|
||||
args.services.map((service) => service.name),
|
||||
).split('\n');
|
||||
}
|
||||
|
||||
return [
|
||||
`Legacy EJClaw multi-service install detected: ${details}`,
|
||||
'This setup is reinstall-only. Remove the legacy services before continuing.',
|
||||
'Suggested cleanup:',
|
||||
...cleanupLines,
|
||||
].join('\n');
|
||||
}
|
||||
@@ -82,31 +82,6 @@ describe('restartStackServices', () => {
|
||||
).toThrow('restart:stack only supports Linux systemd services in this repo');
|
||||
});
|
||||
|
||||
it('fails fast when legacy services are still present', () => {
|
||||
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ejclaw-restart-'));
|
||||
tempRoots.push(tempRoot);
|
||||
|
||||
const execFileSyncImpl = vi.fn();
|
||||
|
||||
expect(() =>
|
||||
restartStackServices(tempRoot, {
|
||||
direct: true,
|
||||
execFileSyncImpl,
|
||||
runningAsRoot: false,
|
||||
serviceManager: 'systemd',
|
||||
serviceId: null,
|
||||
legacyServiceIssues: [
|
||||
{
|
||||
name: 'ejclaw-codex',
|
||||
status: 'stopped',
|
||||
sources: ['systemd-system'],
|
||||
},
|
||||
],
|
||||
}),
|
||||
).toThrow('Legacy EJClaw multi-service install detected');
|
||||
expect(execFileSyncImpl).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('falls back to direct restart when the oneshot unit is not installed for an external caller', () => {
|
||||
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ejclaw-restart-'));
|
||||
tempRoots.push(tempRoot);
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
import { execFileSync } from 'child_process';
|
||||
import os from 'os';
|
||||
import { pathToFileURL } from 'url';
|
||||
|
||||
import {
|
||||
detectLegacyServiceIssues,
|
||||
formatLegacyServiceFailureMessage,
|
||||
type LegacyServiceIssue,
|
||||
} from './legacy-service-guard.js';
|
||||
import { getServiceManager, isRoot } from './platform.js';
|
||||
import { getConfiguredServiceNames } from './service-defs.js';
|
||||
|
||||
@@ -23,8 +17,6 @@ interface RestartStackDeps {
|
||||
serviceManager?: ServiceManager;
|
||||
direct?: boolean;
|
||||
serviceId?: string | null;
|
||||
legacyServiceIssues?: LegacyServiceIssue[];
|
||||
homeDir?: string;
|
||||
}
|
||||
|
||||
function restartStackServicesDirect(
|
||||
@@ -70,24 +62,6 @@ export function restartStackServices(
|
||||
);
|
||||
}
|
||||
|
||||
const legacyServiceIssues =
|
||||
deps.legacyServiceIssues ??
|
||||
detectLegacyServiceIssues(
|
||||
projectRoot,
|
||||
serviceManager,
|
||||
deps.homeDir ?? os.homedir(),
|
||||
);
|
||||
if (legacyServiceIssues.length > 0) {
|
||||
throw new Error(
|
||||
formatLegacyServiceFailureMessage({
|
||||
projectRoot,
|
||||
serviceManager,
|
||||
services: legacyServiceIssues,
|
||||
homeDir: deps.homeDir,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
const services = getConfiguredServiceNames(projectRoot);
|
||||
const execImpl = deps.execFileSyncImpl ?? execFileSync;
|
||||
const systemctlArgs = deps.runningAsRoot ?? isRoot() ? [] : ['--user'];
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export type ServiceKind = 'primary' | 'legacy';
|
||||
export type ServiceKind = 'primary';
|
||||
|
||||
export interface ServiceDef {
|
||||
/** Stable topology kind used by setup/verify logic */
|
||||
@@ -35,23 +35,6 @@ const CURRENT_SERVICE_TEMPLATES: ServiceTemplate[] = [
|
||||
},
|
||||
];
|
||||
|
||||
const LEGACY_SERVICE_TEMPLATES: ServiceTemplate[] = [
|
||||
{
|
||||
kind: 'legacy',
|
||||
name: 'ejclaw-codex',
|
||||
launchdLabel: 'com.ejclaw-codex',
|
||||
description: 'Legacy EJClaw Codex Assistant',
|
||||
logName: 'ejclaw-codex',
|
||||
},
|
||||
{
|
||||
kind: 'legacy',
|
||||
name: 'ejclaw-review',
|
||||
launchdLabel: 'com.ejclaw-review',
|
||||
description: 'Legacy EJClaw Codex Review Assistant',
|
||||
logName: 'ejclaw-review',
|
||||
},
|
||||
];
|
||||
|
||||
function materializeServiceDef(template: ServiceTemplate): ServiceDef {
|
||||
const environmentFile = undefined;
|
||||
const extraEnv =
|
||||
@@ -79,17 +62,6 @@ export function getServiceDefs(projectRoot: string): ServiceDef[] {
|
||||
);
|
||||
}
|
||||
|
||||
export function getLegacyServiceDefs(projectRoot: string): ServiceDef[] {
|
||||
void projectRoot;
|
||||
return LEGACY_SERVICE_TEMPLATES.map((template) =>
|
||||
materializeServiceDef(template),
|
||||
);
|
||||
}
|
||||
|
||||
export function getConfiguredServiceNames(projectRoot: string): string[] {
|
||||
return getServiceDefs(projectRoot).map((def) => def.name);
|
||||
}
|
||||
|
||||
export function getLegacyServiceNames(projectRoot: string): string[] {
|
||||
return getLegacyServiceDefs(projectRoot).map((def) => def.name);
|
||||
}
|
||||
|
||||
@@ -9,11 +9,7 @@ import {
|
||||
buildStackRestartSystemdUnit,
|
||||
buildSystemdUnit,
|
||||
} from './service-renderers.js';
|
||||
import {
|
||||
getLegacyServiceDefs,
|
||||
getServiceDefs,
|
||||
type ServiceDef,
|
||||
} from './service-defs.js';
|
||||
import { getServiceDefs, type ServiceDef } from './service-defs.js';
|
||||
|
||||
/**
|
||||
* Tests for service configuration generation.
|
||||
@@ -189,19 +185,6 @@ describe('service definitions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('keeps legacy service identities available for migration guards', () => {
|
||||
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ejclaw-stack-'));
|
||||
tempRoots.push(tempRoot);
|
||||
|
||||
const defs = getLegacyServiceDefs(tempRoot);
|
||||
|
||||
expect(defs.map((def) => def.name)).toEqual([
|
||||
'ejclaw-codex',
|
||||
'ejclaw-review',
|
||||
]);
|
||||
expect(defs.map((def) => def.kind)).toEqual(['legacy', 'legacy']);
|
||||
});
|
||||
|
||||
it('generates a oneshot stack restart unit', () => {
|
||||
const unit = buildStackRestartSystemdUnit(
|
||||
'/srv/ejclaw',
|
||||
|
||||
@@ -17,10 +17,6 @@ import {
|
||||
getNodePath,
|
||||
getServiceManager,
|
||||
} from './platform.js';
|
||||
import {
|
||||
detectLegacyServiceIssues,
|
||||
formatLegacyServiceFailureMessage,
|
||||
} from './legacy-service-guard.js';
|
||||
import { getServiceDefs } from './service-defs.js';
|
||||
import { setupLaunchd, setupLinux } from './service-installers.js';
|
||||
import { emitStatus } from './status.js';
|
||||
@@ -54,37 +50,6 @@ export async function run(_args: string[]): Promise<void> {
|
||||
);
|
||||
}
|
||||
|
||||
const legacyServiceIssues = detectLegacyServiceIssues(
|
||||
projectRoot,
|
||||
serviceManager,
|
||||
homeDir,
|
||||
);
|
||||
if (legacyServiceIssues.length > 0) {
|
||||
const errorMessage = formatLegacyServiceFailureMessage({
|
||||
projectRoot,
|
||||
serviceManager,
|
||||
homeDir,
|
||||
services: legacyServiceIssues,
|
||||
});
|
||||
logger.error(
|
||||
{ legacyServiceIssues, serviceManager },
|
||||
'Legacy multi-service install detected during setup',
|
||||
);
|
||||
emitStatus('SETUP_SERVICE', {
|
||||
SERVICE_TYPE: serviceManager,
|
||||
NODE_PATH: nodePath,
|
||||
PROJECT_PATH: projectRoot,
|
||||
STATUS: 'failed',
|
||||
ERROR: 'legacy_services_detected',
|
||||
LEGACY_SERVICES: legacyServiceIssues
|
||||
.map((service) => `${service.name}:${service.status}`)
|
||||
.join(','),
|
||||
LOG: 'logs/setup.log',
|
||||
});
|
||||
console.error(errorMessage);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Build first
|
||||
logger.info('Building TypeScript');
|
||||
try {
|
||||
|
||||
@@ -7,13 +7,7 @@
|
||||
*
|
||||
* Uses better-sqlite3 directly (no sqlite3 CLI), platform-aware service checks.
|
||||
*/
|
||||
import os from 'os';
|
||||
|
||||
import { logger } from '../src/logger.js';
|
||||
import {
|
||||
detectLegacyServiceIssues,
|
||||
formatLegacyServiceFailureMessage,
|
||||
} from './legacy-service-guard.js';
|
||||
import { getServiceManager } from './platform.js';
|
||||
import { getServiceDefs } from './service-defs.js';
|
||||
import { emitStatus } from './status.js';
|
||||
@@ -39,21 +33,10 @@ export async function run(_args: string[]): Promise<void> {
|
||||
// 1. Check service statuses
|
||||
const serviceDefs = getServiceDefs(projectRoot);
|
||||
const services = getServiceChecks(serviceDefs, projectRoot, mgr);
|
||||
const legacyServiceIssues = detectLegacyServiceIssues(
|
||||
projectRoot,
|
||||
mgr,
|
||||
os.homedir(),
|
||||
);
|
||||
|
||||
for (const svc of services) {
|
||||
logger.info({ service: svc.name, status: svc.status }, 'Service status');
|
||||
}
|
||||
for (const svc of legacyServiceIssues) {
|
||||
logger.error(
|
||||
{ service: svc.name, status: svc.status },
|
||||
'Legacy service detected during verification',
|
||||
);
|
||||
}
|
||||
|
||||
const credentials = detectCredentials(projectRoot);
|
||||
const channelAuth = detectChannelAuth();
|
||||
@@ -80,10 +63,7 @@ export async function run(_args: string[]): Promise<void> {
|
||||
activeArbiterTasks,
|
||||
},
|
||||
);
|
||||
const legacyServicesSummary = Object.fromEntries(
|
||||
legacyServiceIssues.map((service) => [service.name, service.status]),
|
||||
);
|
||||
const status = legacyServiceIssues.length > 0 ? 'failed' : baseStatus;
|
||||
const status = baseStatus;
|
||||
|
||||
logger.info(
|
||||
{
|
||||
@@ -92,27 +72,12 @@ export async function run(_args: string[]): Promise<void> {
|
||||
tribunalRooms: detectedTribunalRooms,
|
||||
activeArbiterTasks: detectedActiveArbiterTasks,
|
||||
servicesSummary,
|
||||
legacyServicesSummary,
|
||||
},
|
||||
'Verification complete',
|
||||
);
|
||||
if (legacyServiceIssues.length > 0) {
|
||||
logger.error(
|
||||
{
|
||||
cleanup: formatLegacyServiceFailureMessage({
|
||||
projectRoot,
|
||||
serviceManager: mgr,
|
||||
homeDir: os.homedir(),
|
||||
services: legacyServiceIssues,
|
||||
}),
|
||||
},
|
||||
'Verification failed due to legacy multi-service install',
|
||||
);
|
||||
}
|
||||
|
||||
emitStatus('VERIFY', {
|
||||
SERVICES: JSON.stringify(servicesSummary),
|
||||
LEGACY_SERVICES: JSON.stringify(legacyServicesSummary),
|
||||
// Legacy field (keep for backward compatibility)
|
||||
SERVICE: services[0].status,
|
||||
CREDENTIALS: credentials,
|
||||
|
||||
3
shared/verification-snapshot.d.ts
vendored
3
shared/verification-snapshot.d.ts
vendored
@@ -7,6 +7,3 @@ export declare function isVerificationSnapshotExcludedPath(
|
||||
currentPath: string,
|
||||
): boolean;
|
||||
export declare function computeVerificationSnapshotId(repoDir: string): string;
|
||||
export declare function resolveVerificationResponsesDir(
|
||||
hostIpcDir: string,
|
||||
): string;
|
||||
|
||||
@@ -77,7 +77,3 @@ export function computeVerificationSnapshotId(repoDir) {
|
||||
updateVerificationSnapshotHash(hash, repoDir, repoDir);
|
||||
return `fs:${hash.digest('hex').slice(0, 24)}`;
|
||||
}
|
||||
|
||||
export function resolveVerificationResponsesDir(hostIpcDir) {
|
||||
return path.join(hostIpcDir, 'verification-responses');
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ export function enqueueGenericFollowUpAfterDeliveryRetry(args: {
|
||||
deliveryRole: args.deliveryRole,
|
||||
pendingTaskStatus: args.pendingTask?.status ?? null,
|
||||
},
|
||||
'Skipping queued follow-up after reviewer merge_ready delivery because inline finalize will handle the handoff',
|
||||
'No queued follow-up was required after delivery retry',
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -90,6 +90,21 @@ export function enqueueGenericFollowUpAfterDeliveryRetry(args: {
|
||||
nextTurnAction.kind,
|
||||
args.runId,
|
||||
);
|
||||
if (scheduled) {
|
||||
args.log.info(
|
||||
{
|
||||
workItemId: args.workItemId,
|
||||
chatJid: args.chatJid,
|
||||
deliveryRole: args.deliveryRole,
|
||||
taskId: args.pendingTask?.id ?? null,
|
||||
pendingTaskStatus: args.pendingTask?.status ?? null,
|
||||
taskStatus: args.pendingTask?.status ?? null,
|
||||
intentKind: nextTurnAction.kind,
|
||||
},
|
||||
'Queued paired follow-up after delivery retry',
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!scheduled) {
|
||||
args.log.info(
|
||||
{
|
||||
@@ -97,6 +112,7 @@ export function enqueueGenericFollowUpAfterDeliveryRetry(args: {
|
||||
chatJid: args.chatJid,
|
||||
deliveryRole: args.deliveryRole,
|
||||
taskId: args.pendingTask?.id ?? null,
|
||||
pendingTaskStatus: args.pendingTask?.status ?? null,
|
||||
taskStatus: args.pendingTask?.status ?? null,
|
||||
intentKind: nextTurnAction.kind,
|
||||
},
|
||||
|
||||
@@ -105,6 +105,39 @@ describe('message-runtime-rules', () => {
|
||||
).toEqual({ kind: 'none' });
|
||||
});
|
||||
|
||||
it('dispatches reviewer and arbiter delivery success through paired follow-up enqueue when a handoff is pending', () => {
|
||||
expect(
|
||||
resolveFollowUpDispatch({
|
||||
source: 'delivery-success',
|
||||
nextTurnAction: { kind: 'owner-follow-up' },
|
||||
completedRole: 'reviewer',
|
||||
}),
|
||||
).toEqual({
|
||||
kind: 'enqueue',
|
||||
queueKind: 'paired-follow-up',
|
||||
});
|
||||
expect(
|
||||
resolveFollowUpDispatch({
|
||||
source: 'delivery-success',
|
||||
nextTurnAction: { kind: 'finalize-owner-turn' },
|
||||
completedRole: 'reviewer',
|
||||
}),
|
||||
).toEqual({
|
||||
kind: 'enqueue',
|
||||
queueKind: 'paired-follow-up',
|
||||
});
|
||||
expect(
|
||||
resolveFollowUpDispatch({
|
||||
source: 'delivery-success',
|
||||
nextTurnAction: { kind: 'owner-follow-up' },
|
||||
completedRole: 'arbiter',
|
||||
}),
|
||||
).toEqual({
|
||||
kind: 'enqueue',
|
||||
queueKind: 'paired-follow-up',
|
||||
});
|
||||
});
|
||||
|
||||
it('dispatches delivery retry follow-ups through either generic message checks or paired follow-up enqueue', () => {
|
||||
expect(
|
||||
resolveFollowUpDispatch({
|
||||
@@ -132,7 +165,10 @@ describe('message-runtime-rules', () => {
|
||||
nextTurnAction: { kind: 'finalize-owner-turn' },
|
||||
completedRole: 'reviewer',
|
||||
}),
|
||||
).toEqual({ kind: 'none' });
|
||||
).toEqual({
|
||||
kind: 'enqueue',
|
||||
queueKind: 'paired-follow-up',
|
||||
});
|
||||
});
|
||||
|
||||
it('dispatches bot-only follow-ups through inline finalize or paired enqueue', () => {
|
||||
|
||||
@@ -118,6 +118,7 @@ export function resolveNextTurnAction(args: {
|
||||
|
||||
export function resolveFollowUpDispatch(args: {
|
||||
source:
|
||||
| 'delivery-success'
|
||||
| 'owner-delivery-success'
|
||||
| 'delivery-retry'
|
||||
| 'bot-only-follow-up'
|
||||
@@ -128,18 +129,21 @@ export function resolveFollowUpDispatch(args: {
|
||||
sawOutput?: boolean;
|
||||
}): FollowUpDispatch {
|
||||
switch (args.source) {
|
||||
case 'delivery-success':
|
||||
case 'owner-delivery-success':
|
||||
return args.nextTurnAction.kind === 'reviewer-turn'
|
||||
? { kind: 'enqueue', queueKind: 'paired-follow-up' }
|
||||
: { kind: 'none' };
|
||||
if (
|
||||
args.source === 'owner-delivery-success' ||
|
||||
args.completedRole === 'owner'
|
||||
) {
|
||||
return args.nextTurnAction.kind === 'reviewer-turn'
|
||||
? { kind: 'enqueue', queueKind: 'paired-follow-up' }
|
||||
: { kind: 'none' };
|
||||
}
|
||||
return args.nextTurnAction.kind === 'none'
|
||||
? { kind: 'none' }
|
||||
: { kind: 'enqueue', queueKind: 'paired-follow-up' };
|
||||
|
||||
case 'delivery-retry':
|
||||
if (
|
||||
args.completedRole === 'reviewer' &&
|
||||
args.nextTurnAction.kind === 'finalize-owner-turn'
|
||||
) {
|
||||
return { kind: 'none' };
|
||||
}
|
||||
if (args.nextTurnAction.kind === 'none') {
|
||||
return { kind: 'enqueue', queueKind: 'message-check' };
|
||||
}
|
||||
|
||||
@@ -882,15 +882,17 @@ describe('createMessageRuntime', () => {
|
||||
'reviewer final retry',
|
||||
);
|
||||
expect(ownerChannel.sendMessage).not.toHaveBeenCalled();
|
||||
expect(enqueueMessageCheck).not.toHaveBeenCalled();
|
||||
expect(enqueueMessageCheck).toHaveBeenCalledWith(chatJid);
|
||||
expect(logger.info).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
workItemId: 101,
|
||||
chatJid,
|
||||
deliveryRole: 'reviewer',
|
||||
taskId: 'task-review-delivery-role',
|
||||
pendingTaskStatus: 'merge_ready',
|
||||
intentKind: 'finalize-owner-turn',
|
||||
}),
|
||||
'Skipping queued follow-up after reviewer merge_ready delivery because inline finalize will handle the handoff',
|
||||
'Queued paired follow-up after delivery retry',
|
||||
);
|
||||
});
|
||||
|
||||
@@ -975,15 +977,17 @@ describe('createMessageRuntime', () => {
|
||||
'fallback reviewer final retry',
|
||||
);
|
||||
expect(ownerChannel.sendMessage).not.toHaveBeenCalled();
|
||||
expect(enqueueMessageCheck).not.toHaveBeenCalled();
|
||||
expect(enqueueMessageCheck).toHaveBeenCalledWith(chatJid);
|
||||
expect(logger.info).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
workItemId: 102,
|
||||
chatJid,
|
||||
deliveryRole: 'reviewer',
|
||||
taskId: 'task-fallback-review-delivery-role',
|
||||
pendingTaskStatus: 'merge_ready',
|
||||
intentKind: 'finalize-owner-turn',
|
||||
}),
|
||||
'Skipping queued follow-up after reviewer merge_ready delivery because inline finalize will handle the handoff',
|
||||
'Queued paired follow-up after delivery retry',
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1920,6 +1924,287 @@ If your first line is DONE_WITH_CONCERNS, the system will reopen review instead
|
||||
);
|
||||
});
|
||||
|
||||
it('re-enqueues owner after a successful reviewer delivery moves the task back to active', async () => {
|
||||
const chatJid = 'group@test';
|
||||
const group = makeGroup('codex');
|
||||
const ownerChannel = makeChannel(chatJid);
|
||||
const reviewerChannel = makeChannel(chatJid, 'discord-review', false);
|
||||
const enqueueMessageCheck = vi.fn();
|
||||
const pairedTask = {
|
||||
id: 'task-reviewer-delivery-owner-follow-up',
|
||||
chat_jid: chatJid,
|
||||
group_folder: group.folder,
|
||||
owner_service_id: 'claude',
|
||||
reviewer_service_id: 'codex-main',
|
||||
title: null,
|
||||
source_ref: 'HEAD',
|
||||
plan_notes: null,
|
||||
review_requested_at: '2026-03-30T00:00:00.000Z',
|
||||
round_trip_count: 1,
|
||||
status: 'review_ready',
|
||||
arbiter_verdict: null,
|
||||
arbiter_requested_at: null,
|
||||
completion_reason: null,
|
||||
created_at: '2026-03-30T00:00:00.000Z',
|
||||
updated_at: '2026-03-30T00:00:00.000Z',
|
||||
} as any;
|
||||
|
||||
vi.mocked(serviceRouting.hasReviewerLease).mockReturnValue(true);
|
||||
vi.mocked(db.getLatestOpenPairedTaskForChat).mockImplementation(
|
||||
() => pairedTask,
|
||||
);
|
||||
vi.mocked(agentRunner.runAgentProcess).mockImplementation(
|
||||
async (_group, _input, _onProcess, onOutput) => {
|
||||
pairedTask.status = 'active';
|
||||
await onOutput?.({
|
||||
status: 'success',
|
||||
phase: 'final',
|
||||
result: 'DONE_WITH_CONCERNS\nreviewer follow-up needed',
|
||||
newSessionId: 'session-reviewer-delivery-owner-follow-up',
|
||||
});
|
||||
return {
|
||||
status: 'success',
|
||||
result: 'DONE_WITH_CONCERNS\nreviewer follow-up needed',
|
||||
newSessionId: 'session-reviewer-delivery-owner-follow-up',
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
const runtime = createMessageRuntime({
|
||||
assistantName: 'Andy',
|
||||
idleTimeout: 1_000,
|
||||
pollInterval: 1_000,
|
||||
timezone: 'UTC',
|
||||
triggerPattern: /^@Andy\b/i,
|
||||
channels: [ownerChannel, reviewerChannel],
|
||||
queue: {
|
||||
registerProcess: vi.fn(),
|
||||
closeStdin: vi.fn(),
|
||||
notifyIdle: vi.fn(),
|
||||
enqueueMessageCheck,
|
||||
} as any,
|
||||
getRegisteredGroups: () => ({ [chatJid]: group }),
|
||||
getSessions: () => ({}),
|
||||
getLastTimestamp: () => '',
|
||||
setLastTimestamp: vi.fn(),
|
||||
getLastAgentTimestamps: () => ({}),
|
||||
saveState: vi.fn(),
|
||||
persistSession: vi.fn(),
|
||||
clearSession: vi.fn(),
|
||||
});
|
||||
|
||||
const result = await runtime.processGroupMessages(chatJid, {
|
||||
runId: 'run-reviewer-delivery-owner-follow-up',
|
||||
reason: 'messages',
|
||||
});
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(reviewerChannel.sendMessage).toHaveBeenCalledWith(
|
||||
chatJid,
|
||||
'DONE_WITH_CONCERNS\nreviewer follow-up needed',
|
||||
);
|
||||
expect(ownerChannel.sendMessage).not.toHaveBeenCalled();
|
||||
expect(enqueueMessageCheck).toHaveBeenCalledWith(chatJid);
|
||||
expect(logger.info).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
chatJid,
|
||||
runId: 'run-reviewer-delivery-owner-follow-up',
|
||||
completedRole: 'reviewer',
|
||||
taskId: 'task-reviewer-delivery-owner-follow-up',
|
||||
taskStatus: 'active',
|
||||
}),
|
||||
'Queued paired follow-up after successful reviewer/arbiter delivery',
|
||||
);
|
||||
});
|
||||
|
||||
it('re-enqueues finalize-owner after a successful reviewer delivery moves the task to merge_ready', async () => {
|
||||
const chatJid = 'group@test';
|
||||
const group = makeGroup('codex');
|
||||
const ownerChannel = makeChannel(chatJid);
|
||||
const reviewerChannel = makeChannel(chatJid, 'discord-review', false);
|
||||
const enqueueMessageCheck = vi.fn();
|
||||
const pairedTask = {
|
||||
id: 'task-reviewer-delivery-finalize-owner',
|
||||
chat_jid: chatJid,
|
||||
group_folder: group.folder,
|
||||
owner_service_id: 'claude',
|
||||
reviewer_service_id: 'codex-main',
|
||||
title: null,
|
||||
source_ref: 'HEAD',
|
||||
plan_notes: null,
|
||||
review_requested_at: '2026-03-30T00:00:00.000Z',
|
||||
round_trip_count: 1,
|
||||
status: 'review_ready',
|
||||
arbiter_verdict: null,
|
||||
arbiter_requested_at: null,
|
||||
completion_reason: null,
|
||||
created_at: '2026-03-30T00:00:00.000Z',
|
||||
updated_at: '2026-03-30T00:00:00.000Z',
|
||||
} as any;
|
||||
|
||||
vi.mocked(serviceRouting.hasReviewerLease).mockReturnValue(true);
|
||||
vi.mocked(db.getLatestOpenPairedTaskForChat).mockImplementation(
|
||||
() => pairedTask,
|
||||
);
|
||||
vi.mocked(agentRunner.runAgentProcess).mockImplementation(
|
||||
async (_group, _input, _onProcess, onOutput) => {
|
||||
pairedTask.status = 'merge_ready';
|
||||
await onOutput?.({
|
||||
status: 'success',
|
||||
phase: 'final',
|
||||
result: 'DONE\nreview approved',
|
||||
newSessionId: 'session-reviewer-delivery-finalize-owner',
|
||||
});
|
||||
return {
|
||||
status: 'success',
|
||||
result: 'DONE\nreview approved',
|
||||
newSessionId: 'session-reviewer-delivery-finalize-owner',
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
const runtime = createMessageRuntime({
|
||||
assistantName: 'Andy',
|
||||
idleTimeout: 1_000,
|
||||
pollInterval: 1_000,
|
||||
timezone: 'UTC',
|
||||
triggerPattern: /^@Andy\b/i,
|
||||
channels: [ownerChannel, reviewerChannel],
|
||||
queue: {
|
||||
registerProcess: vi.fn(),
|
||||
closeStdin: vi.fn(),
|
||||
notifyIdle: vi.fn(),
|
||||
enqueueMessageCheck,
|
||||
} as any,
|
||||
getRegisteredGroups: () => ({ [chatJid]: group }),
|
||||
getSessions: () => ({}),
|
||||
getLastTimestamp: () => '',
|
||||
setLastTimestamp: vi.fn(),
|
||||
getLastAgentTimestamps: () => ({}),
|
||||
saveState: vi.fn(),
|
||||
persistSession: vi.fn(),
|
||||
clearSession: vi.fn(),
|
||||
});
|
||||
|
||||
const result = await runtime.processGroupMessages(chatJid, {
|
||||
runId: 'run-reviewer-delivery-finalize-owner',
|
||||
reason: 'messages',
|
||||
});
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(reviewerChannel.sendMessage).toHaveBeenCalledWith(
|
||||
chatJid,
|
||||
'DONE\nreview approved',
|
||||
);
|
||||
expect(ownerChannel.sendMessage).not.toHaveBeenCalled();
|
||||
expect(enqueueMessageCheck).toHaveBeenCalledWith(chatJid);
|
||||
expect(logger.info).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
chatJid,
|
||||
runId: 'run-reviewer-delivery-finalize-owner',
|
||||
completedRole: 'reviewer',
|
||||
taskId: 'task-reviewer-delivery-finalize-owner',
|
||||
taskStatus: 'merge_ready',
|
||||
}),
|
||||
'Queued paired follow-up after successful reviewer/arbiter delivery',
|
||||
);
|
||||
});
|
||||
|
||||
it('re-enqueues owner after a successful arbiter delivery moves the task back to active', async () => {
|
||||
const chatJid = 'group@test';
|
||||
const group = makeGroup('codex');
|
||||
const ownerChannel = makeChannel(chatJid);
|
||||
const reviewerChannel = makeChannel(chatJid, 'discord-review', false);
|
||||
const arbiterChannel = makeChannel(chatJid, 'discord-arbiter', false);
|
||||
const enqueueMessageCheck = vi.fn();
|
||||
const pairedTask = {
|
||||
id: 'task-arbiter-delivery-owner-follow-up',
|
||||
chat_jid: chatJid,
|
||||
group_folder: group.folder,
|
||||
owner_service_id: 'claude',
|
||||
reviewer_service_id: 'codex-main',
|
||||
arbiter_service_id: 'claude-arbiter',
|
||||
title: null,
|
||||
source_ref: 'HEAD',
|
||||
plan_notes: null,
|
||||
review_requested_at: '2026-03-30T00:00:00.000Z',
|
||||
round_trip_count: 1,
|
||||
status: 'arbiter_requested',
|
||||
arbiter_verdict: null,
|
||||
arbiter_requested_at: '2026-03-30T00:00:10.000Z',
|
||||
completion_reason: null,
|
||||
created_at: '2026-03-30T00:00:00.000Z',
|
||||
updated_at: '2026-03-30T00:00:00.000Z',
|
||||
} as any;
|
||||
|
||||
vi.mocked(serviceRouting.hasReviewerLease).mockReturnValue(true);
|
||||
vi.mocked(db.getLatestOpenPairedTaskForChat).mockImplementation(
|
||||
() => pairedTask,
|
||||
);
|
||||
vi.mocked(agentRunner.runAgentProcess).mockImplementation(
|
||||
async (_group, _input, _onProcess, onOutput) => {
|
||||
pairedTask.status = 'active';
|
||||
await onOutput?.({
|
||||
status: 'success',
|
||||
phase: 'final',
|
||||
result: 'DONE_WITH_CONCERNS\narbiter says revise',
|
||||
newSessionId: 'session-arbiter-delivery-owner-follow-up',
|
||||
});
|
||||
return {
|
||||
status: 'success',
|
||||
result: 'DONE_WITH_CONCERNS\narbiter says revise',
|
||||
newSessionId: 'session-arbiter-delivery-owner-follow-up',
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
const runtime = createMessageRuntime({
|
||||
assistantName: 'Andy',
|
||||
idleTimeout: 1_000,
|
||||
pollInterval: 1_000,
|
||||
timezone: 'UTC',
|
||||
triggerPattern: /^@Andy\b/i,
|
||||
channels: [ownerChannel, reviewerChannel, arbiterChannel],
|
||||
queue: {
|
||||
registerProcess: vi.fn(),
|
||||
closeStdin: vi.fn(),
|
||||
notifyIdle: vi.fn(),
|
||||
enqueueMessageCheck,
|
||||
} as any,
|
||||
getRegisteredGroups: () => ({ [chatJid]: group }),
|
||||
getSessions: () => ({}),
|
||||
getLastTimestamp: () => '',
|
||||
setLastTimestamp: vi.fn(),
|
||||
getLastAgentTimestamps: () => ({}),
|
||||
saveState: vi.fn(),
|
||||
persistSession: vi.fn(),
|
||||
clearSession: vi.fn(),
|
||||
});
|
||||
|
||||
const result = await runtime.processGroupMessages(chatJid, {
|
||||
runId: 'run-arbiter-delivery-owner-follow-up',
|
||||
reason: 'messages',
|
||||
});
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(arbiterChannel.sendMessage).toHaveBeenCalledWith(
|
||||
chatJid,
|
||||
'DONE_WITH_CONCERNS\narbiter says revise',
|
||||
);
|
||||
expect(ownerChannel.sendMessage).not.toHaveBeenCalled();
|
||||
expect(enqueueMessageCheck).toHaveBeenCalledWith(chatJid);
|
||||
expect(logger.info).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
chatJid,
|
||||
runId: 'run-arbiter-delivery-owner-follow-up',
|
||||
completedRole: 'arbiter',
|
||||
taskId: 'task-arbiter-delivery-owner-follow-up',
|
||||
taskStatus: 'active',
|
||||
}),
|
||||
'Queued paired follow-up after successful reviewer/arbiter delivery',
|
||||
);
|
||||
});
|
||||
|
||||
it('does not enqueue the same reviewer follow-up twice across different runs while task state is unchanged', async () => {
|
||||
const chatJid = 'group@test';
|
||||
const group = makeGroup('codex');
|
||||
|
||||
@@ -343,16 +343,17 @@ export function createMessageRuntime(deps: MessageRuntimeDeps): {
|
||||
const { deliverySucceeded, visiblePhase } =
|
||||
await turnController.finish(outputStatus);
|
||||
|
||||
if (deliverySucceeded && pairedRoom && resolvedDeliveryRole === 'owner') {
|
||||
if (deliverySucceeded && pairedRoom && resolvedDeliveryRole) {
|
||||
const pendingTaskAfterDelivery =
|
||||
getLatestOpenPairedTaskForChat(chatJid);
|
||||
const nextTurnAction = resolveNextTurnAction({
|
||||
taskStatus: pendingTaskAfterDelivery?.status,
|
||||
lastTurnOutputRole: 'owner',
|
||||
lastTurnOutputRole: resolvedDeliveryRole,
|
||||
});
|
||||
const dispatch = resolveFollowUpDispatch({
|
||||
source: 'owner-delivery-success',
|
||||
source: 'delivery-success',
|
||||
nextTurnAction,
|
||||
completedRole: resolvedDeliveryRole,
|
||||
});
|
||||
if (
|
||||
dispatch.kind === 'enqueue' &&
|
||||
@@ -376,8 +377,12 @@ export function createMessageRuntime(deps: MessageRuntimeDeps): {
|
||||
scheduled,
|
||||
},
|
||||
scheduled
|
||||
? 'Queued paired follow-up after successful owner delivery'
|
||||
: 'Skipped duplicate paired follow-up after successful owner delivery while task state was unchanged',
|
||||
? resolvedDeliveryRole === 'owner'
|
||||
? 'Queued paired follow-up after successful owner delivery'
|
||||
: 'Queued paired follow-up after successful reviewer/arbiter delivery'
|
||||
: resolvedDeliveryRole === 'owner'
|
||||
? 'Skipped duplicate paired follow-up after successful owner delivery while task state was unchanged'
|
||||
: 'Skipped duplicate paired follow-up after successful reviewer/arbiter delivery while task state was unchanged',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user