mirror of
https://github.com/anomalyco/opencode.git
synced 2026-04-24 14:55:19 +00:00
wip: discord bot that provisions daytona sandboxes for opencode sessions in threads
This commit is contained in:
30
packages/discord/.env.example
Normal file
30
packages/discord/.env.example
Normal file
@@ -0,0 +1,30 @@
|
||||
# Discord
|
||||
DISCORD_TOKEN=
|
||||
DATABASE_URL= # Neon Postgres connection string
|
||||
ALLOWED_CHANNEL_IDS= # Comma-separated Discord channel IDs
|
||||
DISCORD_ROLE_ID= # Role ID that triggers the bot (optional, for @role mentions)
|
||||
DISCORD_CATEGORY_ID= # Optional category ID that is allowed
|
||||
DISCORD_REQUIRED_ROLE_ID= # Optional role required to talk to bot
|
||||
|
||||
# Daytona
|
||||
DAYTONA_API_KEY=
|
||||
|
||||
# OpenCode (injected into sandboxes)
|
||||
OPENCODE_ZEN_API_KEY=
|
||||
GITHUB_TOKEN= # Optional; enables authenticated gh CLI inside sandbox
|
||||
|
||||
# Observability
|
||||
LOG_LEVEL=info
|
||||
LOG_PRETTY=false
|
||||
HEALTH_HOST=0.0.0.0
|
||||
HEALTH_PORT=8787
|
||||
TURN_ROUTING_MODE=ai # off | heuristic | ai
|
||||
TURN_ROUTING_MODEL=claude-haiku-4-5
|
||||
|
||||
# Bot behavior
|
||||
SANDBOX_REUSE_POLICY=resume_preferred
|
||||
SANDBOX_TIMEOUT_MINUTES=30
|
||||
PAUSED_TTL_MINUTES=180
|
||||
RESUME_HEALTH_TIMEOUT_MS=120000
|
||||
SANDBOX_CREATION_TIMEOUT=180
|
||||
OPENCODE_MODEL=opencode/claude-sonnet-4-5
|
||||
33
packages/discord/.gitignore
vendored
Normal file
33
packages/discord/.gitignore
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
# Dependencies
|
||||
node_modules/
|
||||
|
||||
# Build output
|
||||
dist/
|
||||
|
||||
# Environment
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Bun
|
||||
*.lockb
|
||||
|
||||
# Turbo
|
||||
.turbo/
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
|
||||
# Sensitive
|
||||
.censitive
|
||||
177
packages/discord/AGENTS.md
Normal file
177
packages/discord/AGENTS.md
Normal file
@@ -0,0 +1,177 @@
|
||||
# AGENTS.md
|
||||
|
||||
Guide for coding agents working in this repository.
|
||||
Use this file for build/test commands and coding conventions.
|
||||
|
||||
## Project Snapshot
|
||||
- Stack: Bun + TypeScript (ESM, strict mode)
|
||||
- App: Discord bot that provisions Daytona sandboxes
|
||||
- Persistence: Neon Postgres (`discord_sessions`)
|
||||
- Runtime flow: Discord thread -> sandbox -> OpenCode session
|
||||
- Ops: structured JSON logs + `/healthz` and `/readyz`
|
||||
|
||||
## Repository Map
|
||||
- `src/index.ts`: startup, wiring, graceful shutdown
|
||||
- `src/config.ts`: env schema and parsing (Zod)
|
||||
- `src/discord/`: Discord client + handlers + routing logic
|
||||
- `src/sandbox/`: sandbox lifecycle + OpenCode transport
|
||||
- `src/sessions/store.ts`: Neon-backed session store
|
||||
- `src/db/init.ts`: idempotent DB schema initialization
|
||||
- `src/http/health.ts`: health/readiness HTTP server
|
||||
- `.env.example`: env contract
|
||||
|
||||
## Setup and Run Commands
|
||||
### Install
|
||||
- `bun install`
|
||||
|
||||
### First-time local setup
|
||||
- `cp .env.example .env`
|
||||
- Fill required secrets in `.env`
|
||||
- Initialize schema: `bun run db:init`
|
||||
|
||||
### Development run
|
||||
- Watch mode: `bun run dev`
|
||||
- Normal run: `bun run start`
|
||||
- Dev bootstrap helper: `bun run dev:setup`
|
||||
|
||||
### Static checks
|
||||
- Typecheck: `bun run typecheck`
|
||||
- Build: `bun run build`
|
||||
- Combined check: `bun run check`
|
||||
|
||||
### Health checks
|
||||
- `curl -s http://127.0.0.1:8787/healthz`
|
||||
- `curl -i http://127.0.0.1:8787/readyz`
|
||||
|
||||
## Testing Commands
|
||||
There is no first-party test suite in `src/` yet.
|
||||
Use Bun test commands for new tests.
|
||||
- Run all tests (if present): `bun test`
|
||||
- Run a single test file: `bun test path/to/file.test.ts`
|
||||
- Run one file in watch mode: `bun test --watch path/to/file.test.ts`
|
||||
When adding tests, prefer colocated `*.test.ts` near implementation files.
|
||||
|
||||
## Cursor / Copilot Rules
|
||||
Checked these paths:
|
||||
- `.cursor/rules/`
|
||||
- `.cursorrules`
|
||||
- `.github/copilot-instructions.md`
|
||||
No Cursor/Copilot rule files currently exist in this repo.
|
||||
If added later, update this file and follow those rules.
|
||||
|
||||
## Code Style
|
||||
### TypeScript and modules
|
||||
- Keep code strict-TypeScript compatible.
|
||||
- Use ESM imports/exports only.
|
||||
- Prefer named exports over default exports.
|
||||
- Add explicit return types on exported functions.
|
||||
|
||||
### Imports
|
||||
- Group imports as: external first, then internal.
|
||||
- Use `import type` for type-only imports.
|
||||
- Keep import paths consistent with existing relative style.
|
||||
|
||||
### Formatting
|
||||
- Match existing style:
|
||||
- double quotes
|
||||
- semicolons
|
||||
- trailing commas where appropriate
|
||||
- Keep functions small and focused.
|
||||
- Avoid comments unless logic is non-obvious.
|
||||
|
||||
### Naming
|
||||
- `camelCase`: variables/functions
|
||||
- `PascalCase`: classes/interfaces/type aliases
|
||||
- `UPPER_SNAKE_CASE`: env keys and constants
|
||||
- Log events should be stable (`domain.action.result`).
|
||||
|
||||
### Types and contracts
|
||||
- Reuse shared types from `src/types.ts`.
|
||||
- Preserve `SessionStatus` semantics when adding new states.
|
||||
- Prefer `unknown` over `any` at untrusted boundaries.
|
||||
- Narrow and validate external data before use.
|
||||
|
||||
## Error Handling and Logging
|
||||
- Use `logger` from `src/observability/logger.ts`.
|
||||
- Do not add raw `console.log` in app paths.
|
||||
- Include context fields when available:
|
||||
- `threadId`
|
||||
- `channelId`
|
||||
- `guildId`
|
||||
- `sandboxId`
|
||||
- `sessionId`
|
||||
- Fail fast on invalid config in `src/config.ts`.
|
||||
- Wrap network/process operations in contextual `try/catch`.
|
||||
- Separate recoverable errors from terminal errors.
|
||||
- Never log secret values.
|
||||
|
||||
## Environment and Secrets
|
||||
- Read env only through `getEnv()`.
|
||||
- Update `.env.example` for env schema changes.
|
||||
- Keep auth tokens out of command strings and logs.
|
||||
- Pass runtime secrets via environment variables.
|
||||
|
||||
## Domain-Specific Rules
|
||||
### Session lifecycle
|
||||
- Neon mapping (`thread_id`, `sandbox_id`, `session_id`) is authoritative.
|
||||
- Resume existing sandbox/session before creating replacements.
|
||||
- Recreate only when sandbox is unavailable/destroyed.
|
||||
- If session changes, replay Discord thread history as fallback context.
|
||||
|
||||
### Daytona behavior
|
||||
- `stop()` clears running processes but keeps filesystem state.
|
||||
- `start()` requires process bootstrap (`opencode serve`).
|
||||
- Keep lifecycle transitions deterministic and observable.
|
||||
|
||||
### OpenCode transport
|
||||
- Keep preview token separate from persisted URL when possible.
|
||||
- Send token using `x-daytona-preview-token` header.
|
||||
- Keep retry loops bounded and configurable.
|
||||
|
||||
### Discord handler behavior
|
||||
- Ignore bot/self chatter and respect mention/role gating.
|
||||
- Preserve thread ownership checks for bot-managed threads.
|
||||
- Keep outbound messages chunked for Discord size limits.
|
||||
|
||||
## Non-Obvious Discoveries
|
||||
|
||||
### OpenCode session persistence
|
||||
- Sessions are disk-persistent JSON files in `~/.local/share/opencode/storage/session/<projectID>/`
|
||||
- Sessions survive `opencode serve` restarts if filesystem intact AND process restarts from same git repo directory
|
||||
- Sessions are scoped by `projectID` = git root commit hash (or `"global"` for non-git dirs)
|
||||
- After `daytona.start()`, processes are guaranteed dead - always restart `opencode serve` immediately, don't wait for health first (`src/sandbox/manager.ts:400-420`)
|
||||
|
||||
### Session reattach debugging
|
||||
- If `sessionExists()` returns false but sandbox filesystem is intact, search by title (`Discord thread <threadId>`) via `listSessions()` - session may exist under different ID due to OpenCode internal state changes
|
||||
- Thread lock per `threadId` prevents concurrent create/resume races (`src/sandbox/manager.ts:614-632`)
|
||||
- Never fall back to new sandbox when `daytona.start()` succeeds - filesystem is intact, only OpenCode process needs restart
|
||||
|
||||
### Discord + multiple processes
|
||||
- Multiple bot processes with same `DISCORD_TOKEN` cause race conditions - one succeeds, others fail with `DiscordAPIError[160004]` (thread already created)
|
||||
- PTY sessions with `exec bash -l` stay alive after command exits, leading to duplicate bot runtimes if not cleaned up
|
||||
|
||||
### Sandbox runtime auth
|
||||
- Pass `GITHUB_TOKEN` as process env to `opencode serve` via `sandbox.process.executeCommand()` `env` parameter
|
||||
- Never interpolate tokens into command strings - use `env` parameter in `exec()` options (`src/sandbox/manager.ts:27-72`)
|
||||
|
||||
## Agent Workflow Checklist
|
||||
### Before coding
|
||||
- Read related modules and follow existing patterns.
|
||||
- Prefer narrow, minimal changes over broad refactors.
|
||||
|
||||
### During coding
|
||||
- Keep behavior backwards-compatible unless intentionally changing it.
|
||||
- Keep changes cohesive (schema + store + manager together).
|
||||
- Add/update logs for important lifecycle branches.
|
||||
|
||||
### After coding
|
||||
- Run `bun run typecheck`
|
||||
- Run `bun run build`
|
||||
- Run `bun run db:init` for schema-affecting changes
|
||||
- Smoke-check health endpoints if bootstrap/runtime changed
|
||||
|
||||
## Git/PR Safety for Agents
|
||||
- Do not commit or push unless explicitly requested.
|
||||
- Do not amend commits unless explicitly requested.
|
||||
- Avoid destructive git commands unless explicitly requested.
|
||||
- Summaries should cite changed files and operational impact.
|
||||
24
packages/discord/Dockerfile
Normal file
24
packages/discord/Dockerfile
Normal file
@@ -0,0 +1,24 @@
|
||||
# Stage 1: Install dependencies
|
||||
FROM oven/bun:1.2-alpine AS deps
|
||||
WORKDIR /app
|
||||
COPY package.json bun.lock* ./
|
||||
COPY web/package.json web/
|
||||
COPY discord-bot/package.json discord-bot/
|
||||
RUN bun install --frozen-lockfile
|
||||
|
||||
# Stage 2: Production image
|
||||
FROM oven/bun:1.2-alpine AS runner
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json bun.lock* ./
|
||||
COPY web/package.json web/
|
||||
COPY discord-bot/package.json discord-bot/
|
||||
RUN bun install --production --frozen-lockfile
|
||||
|
||||
# Copy shared DB schema (discord-bot imports from main project)
|
||||
COPY src/db/schema.ts src/db/schema.ts
|
||||
|
||||
# Copy discord bot source
|
||||
COPY discord-bot/src/ discord-bot/src/
|
||||
|
||||
CMD ["bun", "run", "discord-bot/src/index.ts"]
|
||||
59
packages/discord/README.md
Normal file
59
packages/discord/README.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# OpenCord
|
||||
|
||||
Discord bot that provisions [Daytona](https://daytona.io) sandboxes running [OpenCode](https://opencode.ai) sessions. Each Discord thread gets its own isolated sandbox with full conversational context.
|
||||
|
||||
## How It Works
|
||||
|
||||
1. Mention the bot in an allowed channel
|
||||
2. Bot creates a Discord thread and provisions a Daytona sandbox
|
||||
3. OpenCode runs inside the sandbox, responding to messages in the thread
|
||||
4. Inactive threads pause their sandbox automatically; activity resumes them
|
||||
5. Conversational context is preserved across bot restarts
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
bun install
|
||||
cp .env.example .env
|
||||
# Fill in required values in .env
|
||||
bun run db:init
|
||||
bun run dev
|
||||
```
|
||||
|
||||
## Commands
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `bun run dev` | Watch mode |
|
||||
| `bun run start` | Production run |
|
||||
| `bun run db:init` | Initialize/migrate database |
|
||||
| `bun run typecheck` | TypeScript checks |
|
||||
| `bun run build` | Bundle for deployment |
|
||||
| `bun run check` | Typecheck + build |
|
||||
|
||||
## Configuration
|
||||
|
||||
See [`.env.example`](.env.example) for all available environment variables. Required:
|
||||
|
||||
- `DISCORD_TOKEN` — Discord bot token
|
||||
- `DATABASE_URL` — Neon Postgres connection string
|
||||
- `DAYTONA_API_KEY` — Daytona API key
|
||||
- `OPENCODE_ZEN_API_KEY` — OpenCode API key
|
||||
|
||||
## Health Endpoints
|
||||
|
||||
- `GET /healthz` — Liveness check (uptime, Discord status, active sessions)
|
||||
- `GET /readyz` — Readiness check (200 when Discord connected, 503 otherwise)
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
Discord thread
|
||||
└─ message-create handler
|
||||
└─ SandboxManager.resolveSessionForMessage()
|
||||
├─ active? → health check → reuse
|
||||
├─ paused? → daytona.start() → restart opencode → reattach session
|
||||
└─ missing? → create sandbox → clone repo → start opencode → new session
|
||||
```
|
||||
|
||||
Sessions are persisted in Neon Postgres. Sandbox filesystem (including OpenCode session state) survives pause/resume cycles via Daytona stop/start.
|
||||
625
packages/discord/bun.lock
Normal file
625
packages/discord/bun.lock
Normal file
@@ -0,0 +1,625 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"configVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "discord-bot",
|
||||
"dependencies": {
|
||||
"@daytonaio/sdk": "latest",
|
||||
"@neondatabase/serverless": "^0.10",
|
||||
"@opencode-ai/sdk": "latest",
|
||||
"discord.js": "^14",
|
||||
"effect": "^3",
|
||||
"zod": "^3",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest",
|
||||
"@types/node": "^22",
|
||||
"typescript": "^5",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@aws-crypto/crc32": ["@aws-crypto/crc32@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg=="],
|
||||
|
||||
"@aws-crypto/crc32c": ["@aws-crypto/crc32c@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag=="],
|
||||
|
||||
"@aws-crypto/sha1-browser": ["@aws-crypto/sha1-browser@5.2.0", "", { "dependencies": { "@aws-crypto/supports-web-crypto": "^5.2.0", "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-locate-window": "^3.0.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg=="],
|
||||
|
||||
"@aws-crypto/sha256-browser": ["@aws-crypto/sha256-browser@5.2.0", "", { "dependencies": { "@aws-crypto/sha256-js": "^5.2.0", "@aws-crypto/supports-web-crypto": "^5.2.0", "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-locate-window": "^3.0.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw=="],
|
||||
|
||||
"@aws-crypto/sha256-js": ["@aws-crypto/sha256-js@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA=="],
|
||||
|
||||
"@aws-crypto/supports-web-crypto": ["@aws-crypto/supports-web-crypto@5.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg=="],
|
||||
|
||||
"@aws-crypto/util": ["@aws-crypto/util@5.2.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="],
|
||||
|
||||
"@aws-sdk/client-s3": ["@aws-sdk/client-s3@3.988.0", "", { "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.8", "@aws-sdk/credential-provider-node": "^3.972.7", "@aws-sdk/middleware-bucket-endpoint": "^3.972.3", "@aws-sdk/middleware-expect-continue": "^3.972.3", "@aws-sdk/middleware-flexible-checksums": "^3.972.6", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-location-constraint": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", "@aws-sdk/middleware-sdk-s3": "^3.972.8", "@aws-sdk/middleware-ssec": "^3.972.3", "@aws-sdk/middleware-user-agent": "^3.972.8", "@aws-sdk/region-config-resolver": "^3.972.3", "@aws-sdk/signature-v4-multi-region": "3.988.0", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.988.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", "@aws-sdk/util-user-agent-node": "^3.972.6", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.23.0", "@smithy/eventstream-serde-browser": "^4.2.8", "@smithy/eventstream-serde-config-resolver": "^4.3.8", "@smithy/eventstream-serde-node": "^4.2.8", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-blob-browser": "^4.2.9", "@smithy/hash-node": "^4.2.8", "@smithy/hash-stream-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/md5-js": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", "@smithy/middleware-endpoint": "^4.4.14", "@smithy/middleware-retry": "^4.4.31", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.10", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.11.3", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.30", "@smithy/util-defaults-mode-node": "^4.2.33", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/util-stream": "^4.5.12", "@smithy/util-utf8": "^4.2.0", "@smithy/util-waiter": "^4.2.8", "tslib": "^2.6.2" } }, "sha512-mt7AdkieJJ5hEKeCxH4sdTTd679shUjo/cUvNY0fUHgQIPZa1jRuekTXnRytRrEwdrZWJDx56n1S8ism2uX7jg=="],
|
||||
|
||||
"@aws-sdk/client-sso": ["@aws-sdk/client-sso@3.988.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.8", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", "@aws-sdk/middleware-user-agent": "^3.972.8", "@aws-sdk/region-config-resolver": "^3.972.3", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.988.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", "@aws-sdk/util-user-agent-node": "^3.972.6", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.23.0", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", "@smithy/middleware-endpoint": "^4.4.14", "@smithy/middleware-retry": "^4.4.31", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.10", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.11.3", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.30", "@smithy/util-defaults-mode-node": "^4.2.33", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-ThqQ7aF1k0Zz4yJRwegHw+T1rM3a7ZPvvEUSEdvn5Z8zTeWgJAbtqW/6ejPsMLmFOlHgNcwDQN/e69OvtEOoIQ=="],
|
||||
|
||||
"@aws-sdk/core": ["@aws-sdk/core@3.973.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@aws-sdk/xml-builder": "^3.972.4", "@smithy/core": "^3.23.0", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", "@smithy/smithy-client": "^4.11.3", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-WeYJ2sfvRLbbUIrjGMUXcEHGu5SJk53jz3K9F8vFP42zWyROzPJ2NB6lMu9vWl5hnMwzwabX7pJc9Euh3JyMGw=="],
|
||||
|
||||
"@aws-sdk/crc64-nvme": ["@aws-sdk/crc64-nvme@3.972.0", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-ThlLhTqX68jvoIVv+pryOdb5coP1cX1/MaTbB9xkGDCbWbsqQcLqzPxuSoW1DCnAAIacmXCWpzUNOB9pv+xXQw=="],
|
||||
|
||||
"@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.6", "", { "dependencies": { "@aws-sdk/core": "^3.973.8", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-+dYEBWgTqkQQHFUllvBL8SLyXyLKWdxLMD1LmKJRvmb0NMJuaJFG/qg78C+LE67eeGbipYcE+gJ48VlLBGHlMw=="],
|
||||
|
||||
"@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.8", "", { "dependencies": { "@aws-sdk/core": "^3.973.8", "@aws-sdk/types": "^3.973.1", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/node-http-handler": "^4.4.10", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.11.3", "@smithy/types": "^4.12.0", "@smithy/util-stream": "^4.5.12", "tslib": "^2.6.2" } }, "sha512-z3QkozMV8kOFisN2pgRag/f0zPDrw96mY+ejAM0xssV/+YQ2kklbylRNI/TcTQUDnGg0yPxNjyV6F2EM2zPTwg=="],
|
||||
|
||||
"@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.972.6", "", { "dependencies": { "@aws-sdk/core": "^3.973.8", "@aws-sdk/credential-provider-env": "^3.972.6", "@aws-sdk/credential-provider-http": "^3.972.8", "@aws-sdk/credential-provider-login": "^3.972.6", "@aws-sdk/credential-provider-process": "^3.972.6", "@aws-sdk/credential-provider-sso": "^3.972.6", "@aws-sdk/credential-provider-web-identity": "^3.972.6", "@aws-sdk/nested-clients": "3.988.0", "@aws-sdk/types": "^3.973.1", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-6tkIYFv3sZH1XsjQq+veOmx8XWRnyqTZ5zx/sMtdu/xFRIzrJM1Y2wAXeCJL1rhYSB7uJSZ1PgALI2WVTj78ow=="],
|
||||
|
||||
"@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.6", "", { "dependencies": { "@aws-sdk/core": "^3.973.8", "@aws-sdk/nested-clients": "3.988.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-LXsoBoaTSGHdRCQXlWSA0CHHh05KWncb592h9ElklnPus++8kYn1Ic6acBR4LKFQ0RjjMVgwe5ypUpmTSUOjPA=="],
|
||||
|
||||
"@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.7", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.6", "@aws-sdk/credential-provider-http": "^3.972.8", "@aws-sdk/credential-provider-ini": "^3.972.6", "@aws-sdk/credential-provider-process": "^3.972.6", "@aws-sdk/credential-provider-sso": "^3.972.6", "@aws-sdk/credential-provider-web-identity": "^3.972.6", "@aws-sdk/types": "^3.973.1", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-PuJ1IkISG7ZDpBFYpGotaay6dYtmriBYuHJ/Oko4VHxh8YN5vfoWnMNYFEWuzOfyLmP7o9kDVW0BlYIpb3skvw=="],
|
||||
|
||||
"@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.6", "", { "dependencies": { "@aws-sdk/core": "^3.973.8", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-Yf34cjIZJHVnD92jnVYy3tNjM+Q4WJtffLK2Ehn0nKpZfqd1m7SI0ra22Lym4C53ED76oZENVSS2wimoXJtChQ=="],
|
||||
|
||||
"@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.972.6", "", { "dependencies": { "@aws-sdk/client-sso": "3.988.0", "@aws-sdk/core": "^3.973.8", "@aws-sdk/token-providers": "3.988.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-2+5UVwUYdD4BBOkLpKJ11MQ8wQeyJGDVMDRH5eWOULAh9d6HJq07R69M/mNNMC9NTjr3mB1T0KGDn4qyQh5jzg=="],
|
||||
|
||||
"@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.6", "", { "dependencies": { "@aws-sdk/core": "^3.973.8", "@aws-sdk/nested-clients": "3.988.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-pdJzwKtlDxBnvZ04pWMqttijmkUIlwOsS0GcxCjzEVyUMpARysl0S0ks74+gs2Pdev3Ujz+BTAjOc1tQgAxGqA=="],
|
||||
|
||||
"@aws-sdk/lib-storage": ["@aws-sdk/lib-storage@3.988.0", "", { "dependencies": { "@smithy/abort-controller": "^4.2.8", "@smithy/middleware-endpoint": "^4.4.14", "@smithy/smithy-client": "^4.11.3", "buffer": "5.6.0", "events": "3.3.0", "stream-browserify": "3.0.0", "tslib": "^2.6.2" }, "peerDependencies": { "@aws-sdk/client-s3": "^3.988.0" } }, "sha512-EZ6US/oV1n7WtvVZ5FR80g8pA7xU92lBsvjibANVm/uHr8FOqPmSqQV9KvwzRmSdp0O6J1kXxi9LqmwgJVlN/A=="],
|
||||
|
||||
"@aws-sdk/middleware-bucket-endpoint": ["@aws-sdk/middleware-bucket-endpoint@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-arn-parser": "^3.972.2", "@smithy/node-config-provider": "^4.3.8", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "@smithy/util-config-provider": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-fmbgWYirF67YF1GfD7cg5N6HHQ96EyRNx/rDIrTF277/zTWVuPI2qS/ZHgofwR1NZPe/NWvoppflQY01LrbVLg=="],
|
||||
|
||||
"@aws-sdk/middleware-expect-continue": ["@aws-sdk/middleware-expect-continue@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-4msC33RZsXQpUKR5QR4HnvBSNCPLGHmB55oDiROqqgyOc+TOfVu2xgi5goA7ms6MdZLeEh2905UfWMnMMF4mRg=="],
|
||||
|
||||
"@aws-sdk/middleware-flexible-checksums": ["@aws-sdk/middleware-flexible-checksums@3.972.6", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@aws-crypto/crc32c": "5.2.0", "@aws-crypto/util": "5.2.0", "@aws-sdk/core": "^3.973.8", "@aws-sdk/crc64-nvme": "3.972.0", "@aws-sdk/types": "^3.973.1", "@smithy/is-array-buffer": "^4.2.0", "@smithy/node-config-provider": "^4.3.8", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-stream": "^4.5.12", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-g5DadWO58IgQKuq+uLL3pLohOwLiA67gB49xj8694BW+LpHLNu/tjCqwLfIaWvZyABbv0LXeNiiTuTnjdgkZWw=="],
|
||||
|
||||
"@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-aknPTb2M+G3s+0qLCx4Li/qGZH8IIYjugHMv15JTYMe6mgZO8VBpYgeGYsNMGCqCZOcWzuf900jFBG5bopfzmA=="],
|
||||
|
||||
"@aws-sdk/middleware-location-constraint": ["@aws-sdk/middleware-location-constraint@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-nIg64CVrsXp67vbK0U1/Is8rik3huS3QkRHn2DRDx4NldrEFMgdkZGI/+cZMKD9k4YOS110Dfu21KZLHrFA/1g=="],
|
||||
|
||||
"@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-Ftg09xNNRqaz9QNzlfdQWfpqMCJbsQdnZVJP55jfhbKi1+FTWxGuvfPoBhDHIovqWKjqbuiew3HuhxbJ0+OjgA=="],
|
||||
|
||||
"@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-PY57QhzNuXHnwbJgbWYTrqIDHYSeOlhfYERTAuc16LKZpTZRJUjzBFokp9hF7u1fuGeE3D70ERXzdbMBOqQz7Q=="],
|
||||
|
||||
"@aws-sdk/middleware-sdk-s3": ["@aws-sdk/middleware-sdk-s3@3.972.8", "", { "dependencies": { "@aws-sdk/core": "^3.973.8", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-arn-parser": "^3.972.2", "@smithy/core": "^3.23.0", "@smithy/node-config-provider": "^4.3.8", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", "@smithy/smithy-client": "^4.11.3", "@smithy/types": "^4.12.0", "@smithy/util-config-provider": "^4.2.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-stream": "^4.5.12", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-/yJdahpN/q3Dc88qXBTQVZfnXryLnxfCoP4hGClbKjuF0VCMxrz3il7sj0GhIkEQt5OM5+lA88XrvbjjuwSxIg=="],
|
||||
|
||||
"@aws-sdk/middleware-ssec": ["@aws-sdk/middleware-ssec@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-dU6kDuULN3o3jEHcjm0c4zWJlY1zWVkjG9NPe9qxYLLpcbdj5kRYBS2DdWYD+1B9f910DezRuws7xDEqKkHQIg=="],
|
||||
|
||||
"@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.972.8", "", { "dependencies": { "@aws-sdk/core": "^3.973.8", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.988.0", "@smithy/core": "^3.23.0", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-3PGL+Kvh1PhB0EeJeqNqOWQgipdqFheO4OUKc6aYiFwEpM5t9AyE5hjjxZ5X6iSj8JiduWFZLPwASzF6wQRgFg=="],
|
||||
|
||||
"@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.988.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.8", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", "@aws-sdk/middleware-user-agent": "^3.972.8", "@aws-sdk/region-config-resolver": "^3.972.3", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.988.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", "@aws-sdk/util-user-agent-node": "^3.972.6", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.23.0", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", "@smithy/middleware-endpoint": "^4.4.14", "@smithy/middleware-retry": "^4.4.31", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.10", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.11.3", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.30", "@smithy/util-defaults-mode-node": "^4.2.33", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-OgYV9k1oBCQ6dOM+wWAMNNehXA8L4iwr7ydFV+JDHyuuu0Ko7tDXnLEtEmeQGYRcAFU3MGasmlBkMB8vf4POrg=="],
|
||||
|
||||
"@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/config-resolver": "^4.4.6", "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-v4J8qYAWfOMcZ4MJUyatntOicTzEMaU7j3OpkRCGGFSL2NgXQ5VbxauIyORA+pxdKZ0qQG2tCQjQjZDlXEC3Ow=="],
|
||||
|
||||
"@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.988.0", "", { "dependencies": { "@aws-sdk/middleware-sdk-s3": "^3.972.8", "@aws-sdk/types": "^3.973.1", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-SXwhbe2v0Jno7QLIBmZWAL2eVzGmXkfLLy0WkM6ZJVhE0SFUcnymDwMUA1oMDUvyArzvKBiU8khQ2ImheCKOHQ=="],
|
||||
|
||||
"@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.988.0", "", { "dependencies": { "@aws-sdk/core": "^3.973.8", "@aws-sdk/nested-clients": "3.988.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-xvXVlRVKHnF2h6fgWBm64aPP5J+58aJyGfRrQa/uFh8a9mcK68mLfJOYq+ZSxQy/UN3McafJ2ILAy7IWzT9kRw=="],
|
||||
|
||||
"@aws-sdk/types": ["@aws-sdk/types@3.973.1", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-DwHBiMNOB468JiX6+i34c+THsKHErYUdNQ3HexeXZvVn4zouLjgaS4FejiGSi2HyBuzuyHg7SuOPmjSvoU9NRg=="],
|
||||
|
||||
"@aws-sdk/util-arn-parser": ["@aws-sdk/util-arn-parser@3.972.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-VkykWbqMjlSgBFDyrY3nOSqupMc6ivXuGmvci6Q3NnLq5kC+mKQe2QBZ4nrWRE/jqOxeFP2uYzLtwncYYcvQDg=="],
|
||||
|
||||
"@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.988.0", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-endpoints": "^3.2.8", "tslib": "^2.6.2" } }, "sha512-HuXu4boeUWU0DQiLslbgdvuQ4ZMCo4Lsk97w8BIUokql2o9MvjE5dwqI5pzGt0K7afO1FybjidUQVTMLuZNTOA=="],
|
||||
|
||||
"@aws-sdk/util-locate-window": ["@aws-sdk/util-locate-window@3.965.4", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-H1onv5SkgPBK2P6JR2MjGgbOnttoNzSPIRoeZTNPZYyaplwGg50zS3amXvXqF0/qfXpWEC9rLWU564QTB9bSog=="],
|
||||
|
||||
"@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-JurOwkRUcXD/5MTDBcqdyQ9eVedtAsZgw5rBwktsPTN7QtPiS2Ld1jkJepNgYoCufz1Wcut9iup7GJDoIHp8Fw=="],
|
||||
|
||||
"@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.972.6", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "^3.972.8", "@aws-sdk/types": "^3.973.1", "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-966xH8TPqkqOXP7EwnEThcKKz0SNP9kVJBKd9M8bNXE4GSqVouMKKnFBwYnzbWVKuLXubzX5seokcX4a0JLJIA=="],
|
||||
|
||||
"@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.4", "", { "dependencies": { "@smithy/types": "^4.12.0", "fast-xml-parser": "5.3.4", "tslib": "^2.6.2" } }, "sha512-0zJ05ANfYqI6+rGqj8samZBFod0dPPousBjLEqg8WdxSgbMAkRgLyn81lP215Do0rFJ/17LIXwr7q0yK24mP6Q=="],
|
||||
|
||||
"@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.2.3", "", {}, "sha512-oLvsaPMTBejkkmHhjf09xTgk71mOqyr/409NKhRIL08If7AhVfUsJhVsx386uJaqNd42v9kWamQ9lFbkoC2dYw=="],
|
||||
|
||||
"@daytonaio/api-client": ["@daytonaio/api-client@0.141.0", "", { "dependencies": { "axios": "^1.6.1" } }, "sha512-DSPCurIEjfFyXCd07jkDgfsoFppVhTLyIJdvfb0LgG1EgV75BPqqzk2WM4ragBFJUuK2URF5CK7qkaHW0AXKMA=="],
|
||||
|
||||
"@daytonaio/sdk": ["@daytonaio/sdk@0.141.0", "", { "dependencies": { "@aws-sdk/client-s3": "^3.787.0", "@aws-sdk/lib-storage": "^3.798.0", "@daytonaio/api-client": "0.141.0", "@daytonaio/toolbox-api-client": "0.141.0", "@iarna/toml": "^2.2.5", "@opentelemetry/api": "^1.9.0", "@opentelemetry/exporter-trace-otlp-http": "^0.207.0", "@opentelemetry/instrumentation-http": "^0.207.0", "@opentelemetry/otlp-exporter-base": "0.207.0", "@opentelemetry/resources": "2.2.0", "@opentelemetry/sdk-node": "^0.207.0", "@opentelemetry/sdk-trace-base": "^2.2.0", "@opentelemetry/semantic-conventions": "^1.37.0", "axios": "^1.13.5", "busboy": "^1.0.0", "dotenv": "^17.0.1", "expand-tilde": "^2.0.2", "fast-glob": "^3.3.0", "form-data": "^4.0.4", "isomorphic-ws": "^5.0.0", "pathe": "^2.0.3", "shell-quote": "^1.8.2", "tar": "^7.5.7" } }, "sha512-JUopkS9SkO7h4WN8CjparOrP9k954euOF5KG//PeCEFOxUWTPFOME70GrmHXQKa1qkdZiF/4tz9jtZ744B1I2w=="],
|
||||
|
||||
"@daytonaio/toolbox-api-client": ["@daytonaio/toolbox-api-client@0.141.0", "", { "dependencies": { "axios": "^1.6.1" } }, "sha512-KGkCLDLAltd9FCic3PhSJGrTp3RwGsUwWEGp5vyWZFQGWpJV8CVp08CH5SBdo4YhuqFUVlyQcwha1HpzpVH++A=="],
|
||||
|
||||
"@discordjs/builders": ["@discordjs/builders@1.13.1", "", { "dependencies": { "@discordjs/formatters": "^0.6.2", "@discordjs/util": "^1.2.0", "@sapphire/shapeshift": "^4.0.0", "discord-api-types": "^0.38.33", "fast-deep-equal": "^3.1.3", "ts-mixer": "^6.0.4", "tslib": "^2.6.3" } }, "sha512-cOU0UDHc3lp/5nKByDxkmRiNZBpdp0kx55aarbiAfakfKJHlxv/yFW1zmIqCAmwH5CRlrH9iMFKJMpvW4DPB+w=="],
|
||||
|
||||
"@discordjs/collection": ["@discordjs/collection@1.5.3", "", {}, "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ=="],
|
||||
|
||||
"@discordjs/formatters": ["@discordjs/formatters@0.6.2", "", { "dependencies": { "discord-api-types": "^0.38.33" } }, "sha512-y4UPwWhH6vChKRkGdMB4odasUbHOUwy7KL+OVwF86PvT6QVOwElx+TiI1/6kcmcEe+g5YRXJFiXSXUdabqZOvQ=="],
|
||||
|
||||
"@discordjs/rest": ["@discordjs/rest@2.6.0", "", { "dependencies": { "@discordjs/collection": "^2.1.1", "@discordjs/util": "^1.1.1", "@sapphire/async-queue": "^1.5.3", "@sapphire/snowflake": "^3.5.3", "@vladfrangu/async_event_emitter": "^2.4.6", "discord-api-types": "^0.38.16", "magic-bytes.js": "^1.10.0", "tslib": "^2.6.3", "undici": "6.21.3" } }, "sha512-RDYrhmpB7mTvmCKcpj+pc5k7POKszS4E2O9TYc+U+Y4iaCP+r910QdO43qmpOja8LRr1RJ0b3U+CqVsnPqzf4w=="],
|
||||
|
||||
"@discordjs/util": ["@discordjs/util@1.2.0", "", { "dependencies": { "discord-api-types": "^0.38.33" } }, "sha512-3LKP7F2+atl9vJFhaBjn4nOaSWahZ/yWjOvA4e5pnXkt2qyXRCHLxoBQy81GFtLGCq7K9lPm9R517M1U+/90Qg=="],
|
||||
|
||||
"@discordjs/ws": ["@discordjs/ws@1.2.3", "", { "dependencies": { "@discordjs/collection": "^2.1.0", "@discordjs/rest": "^2.5.1", "@discordjs/util": "^1.1.0", "@sapphire/async-queue": "^1.5.2", "@types/ws": "^8.5.10", "@vladfrangu/async_event_emitter": "^2.2.4", "discord-api-types": "^0.38.1", "tslib": "^2.6.2", "ws": "^8.17.0" } }, "sha512-wPlQDxEmlDg5IxhJPuxXr3Vy9AjYq5xCvFWGJyD7w7Np8ZGu+Mc+97LCoEc/+AYCo2IDpKioiH0/c/mj5ZR9Uw=="],
|
||||
|
||||
"@grpc/grpc-js": ["@grpc/grpc-js@1.14.3", "", { "dependencies": { "@grpc/proto-loader": "^0.8.0", "@js-sdsl/ordered-map": "^4.4.2" } }, "sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA=="],
|
||||
|
||||
"@grpc/proto-loader": ["@grpc/proto-loader@0.8.0", "", { "dependencies": { "lodash.camelcase": "^4.3.0", "long": "^5.0.0", "protobufjs": "^7.5.3", "yargs": "^17.7.2" }, "bin": { "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" } }, "sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ=="],
|
||||
|
||||
"@iarna/toml": ["@iarna/toml@2.2.5", "", {}, "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg=="],
|
||||
|
||||
"@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="],
|
||||
|
||||
"@js-sdsl/ordered-map": ["@js-sdsl/ordered-map@4.4.2", "", {}, "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw=="],
|
||||
|
||||
"@neondatabase/serverless": ["@neondatabase/serverless@0.10.4", "", { "dependencies": { "@types/pg": "8.11.6" } }, "sha512-2nZuh3VUO9voBauuh+IGYRhGU/MskWHt1IuZvHcJw6GLjDgtqj/KViKo7SIrLdGLdot7vFbiRRw+BgEy3wT9HA=="],
|
||||
|
||||
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
|
||||
|
||||
"@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="],
|
||||
|
||||
"@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
|
||||
|
||||
"@opencode-ai/sdk": ["@opencode-ai/sdk@1.1.60", "", {}, "sha512-RVbQQCXLr1yXCYnlYiIF0t4lMUPCh4x2EU/FfvnfiDaBAwKOoIvQUjZd+AXSpjbConum6Wa2PzV65waFhtix1w=="],
|
||||
|
||||
"@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
|
||||
|
||||
"@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.207.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-lAb0jQRVyleQQGiuuvCOTDVspc14nx6XJjP4FspJ1sNARo3Regq4ZZbrc3rN4b1TYSuUCvgH+UXUPug4SLOqEQ=="],
|
||||
|
||||
"@opentelemetry/context-async-hooks": ["@opentelemetry/context-async-hooks@2.2.0", "", { "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-qRkLWiUEZNAmYapZ7KGS5C4OmBLcP/H2foXeOEaowYCR0wi89fHejrfYfbuLVCMLp/dWZXKvQusdbUEZjERfwQ=="],
|
||||
|
||||
"@opentelemetry/core": ["@opentelemetry/core@2.2.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw=="],
|
||||
|
||||
"@opentelemetry/exporter-logs-otlp-grpc": ["@opentelemetry/exporter-logs-otlp-grpc@0.207.0", "", { "dependencies": { "@grpc/grpc-js": "^1.7.1", "@opentelemetry/core": "2.2.0", "@opentelemetry/otlp-exporter-base": "0.207.0", "@opentelemetry/otlp-grpc-exporter-base": "0.207.0", "@opentelemetry/otlp-transformer": "0.207.0", "@opentelemetry/sdk-logs": "0.207.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-K92RN+kQGTMzFDsCzsYNGqOsXRUnko/Ckk+t/yPJao72MewOLgBUTWVHhebgkNfRCYqDz1v3K0aPT9OJkemvgg=="],
|
||||
|
||||
"@opentelemetry/exporter-logs-otlp-http": ["@opentelemetry/exporter-logs-otlp-http@0.207.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.207.0", "@opentelemetry/core": "2.2.0", "@opentelemetry/otlp-exporter-base": "0.207.0", "@opentelemetry/otlp-transformer": "0.207.0", "@opentelemetry/sdk-logs": "0.207.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-JpOh7MguEUls8eRfkVVW3yRhClo5b9LqwWTOg8+i4gjr/+8eiCtquJnC7whvpTIGyff06cLZ2NsEj+CVP3Mjeg=="],
|
||||
|
||||
"@opentelemetry/exporter-logs-otlp-proto": ["@opentelemetry/exporter-logs-otlp-proto@0.207.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.207.0", "@opentelemetry/core": "2.2.0", "@opentelemetry/otlp-exporter-base": "0.207.0", "@opentelemetry/otlp-transformer": "0.207.0", "@opentelemetry/resources": "2.2.0", "@opentelemetry/sdk-logs": "0.207.0", "@opentelemetry/sdk-trace-base": "2.2.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-RQJEV/K6KPbQrIUbsrRkEe0ufks1o5OGLHy6jbDD8tRjeCsbFHWfg99lYBRqBV33PYZJXsigqMaAbjWGTFYzLw=="],
|
||||
|
||||
"@opentelemetry/exporter-metrics-otlp-grpc": ["@opentelemetry/exporter-metrics-otlp-grpc@0.207.0", "", { "dependencies": { "@grpc/grpc-js": "^1.7.1", "@opentelemetry/core": "2.2.0", "@opentelemetry/exporter-metrics-otlp-http": "0.207.0", "@opentelemetry/otlp-exporter-base": "0.207.0", "@opentelemetry/otlp-grpc-exporter-base": "0.207.0", "@opentelemetry/otlp-transformer": "0.207.0", "@opentelemetry/resources": "2.2.0", "@opentelemetry/sdk-metrics": "2.2.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-6flX89W54gkwmqYShdcTBR1AEF5C1Ob0O8pDgmLPikTKyEv27lByr9yBmO5WrP0+5qJuNPHrLfgFQFYi6npDGA=="],
|
||||
|
||||
"@opentelemetry/exporter-metrics-otlp-http": ["@opentelemetry/exporter-metrics-otlp-http@0.207.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/otlp-exporter-base": "0.207.0", "@opentelemetry/otlp-transformer": "0.207.0", "@opentelemetry/resources": "2.2.0", "@opentelemetry/sdk-metrics": "2.2.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-fG8FAJmvXOrKXGIRN8+y41U41IfVXxPRVwyB05LoMqYSjugx/FSBkMZUZXUT/wclTdmBKtS5MKoi0bEKkmRhSw=="],
|
||||
|
||||
"@opentelemetry/exporter-metrics-otlp-proto": ["@opentelemetry/exporter-metrics-otlp-proto@0.207.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/exporter-metrics-otlp-http": "0.207.0", "@opentelemetry/otlp-exporter-base": "0.207.0", "@opentelemetry/otlp-transformer": "0.207.0", "@opentelemetry/resources": "2.2.0", "@opentelemetry/sdk-metrics": "2.2.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-kDBxiTeQjaRlUQzS1COT9ic+et174toZH6jxaVuVAvGqmxOkgjpLOjrI5ff8SMMQE69r03L3Ll3nPKekLopLwg=="],
|
||||
|
||||
"@opentelemetry/exporter-prometheus": ["@opentelemetry/exporter-prometheus@0.207.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/resources": "2.2.0", "@opentelemetry/sdk-metrics": "2.2.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-Y5p1s39FvIRmU+F1++j7ly8/KSqhMmn6cMfpQqiDCqDjdDHwUtSq0XI0WwL3HYGnZeaR/VV4BNmsYQJ7GAPrhw=="],
|
||||
|
||||
"@opentelemetry/exporter-trace-otlp-grpc": ["@opentelemetry/exporter-trace-otlp-grpc@0.207.0", "", { "dependencies": { "@grpc/grpc-js": "^1.7.1", "@opentelemetry/core": "2.2.0", "@opentelemetry/otlp-exporter-base": "0.207.0", "@opentelemetry/otlp-grpc-exporter-base": "0.207.0", "@opentelemetry/otlp-transformer": "0.207.0", "@opentelemetry/resources": "2.2.0", "@opentelemetry/sdk-trace-base": "2.2.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-7u2ZmcIx6D4KG/+5np4X2qA0o+O0K8cnUDhR4WI/vr5ZZ0la9J9RG+tkSjC7Yz+2XgL6760gSIM7/nyd3yaBLA=="],
|
||||
|
||||
"@opentelemetry/exporter-trace-otlp-http": ["@opentelemetry/exporter-trace-otlp-http@0.207.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/otlp-exporter-base": "0.207.0", "@opentelemetry/otlp-transformer": "0.207.0", "@opentelemetry/resources": "2.2.0", "@opentelemetry/sdk-trace-base": "2.2.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-HSRBzXHIC7C8UfPQdu15zEEoBGv0yWkhEwxqgPCHVUKUQ9NLHVGXkVrf65Uaj7UwmAkC1gQfkuVYvLlD//AnUQ=="],
|
||||
|
||||
"@opentelemetry/exporter-trace-otlp-proto": ["@opentelemetry/exporter-trace-otlp-proto@0.207.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/otlp-exporter-base": "0.207.0", "@opentelemetry/otlp-transformer": "0.207.0", "@opentelemetry/resources": "2.2.0", "@opentelemetry/sdk-trace-base": "2.2.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-ruUQB4FkWtxHjNmSXjrhmJZFvyMm+tBzHyMm7YPQshApy4wvZUTcrpPyP/A/rCl/8M4BwoVIZdiwijMdbZaq4w=="],
|
||||
|
||||
"@opentelemetry/exporter-zipkin": ["@opentelemetry/exporter-zipkin@2.2.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/resources": "2.2.0", "@opentelemetry/sdk-trace-base": "2.2.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0" } }, "sha512-VV4QzhGCT7cWrGasBWxelBjqbNBbyHicWWS/66KoZoe9BzYwFB72SH2/kkc4uAviQlO8iwv2okIJy+/jqqEHTg=="],
|
||||
|
||||
"@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.207.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.207.0", "import-in-the-middle": "^2.0.0", "require-in-the-middle": "^8.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-y6eeli9+TLKnznrR8AZlQMSJT7wILpXH+6EYq5Vf/4Ao+huI7EedxQHwRgVUOMLFbe7VFDvHJrX9/f4lcwnJsA=="],
|
||||
|
||||
"@opentelemetry/instrumentation-http": ["@opentelemetry/instrumentation-http@0.207.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/instrumentation": "0.207.0", "@opentelemetry/semantic-conventions": "^1.29.0", "forwarded-parse": "2.1.2" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-FC4i5hVixTzuhg4SV2ycTEAYx+0E2hm+GwbdoVPSA6kna0pPVI4etzaA9UkpJ9ussumQheFXP6rkGIaFJjMxsw=="],
|
||||
|
||||
"@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.207.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/otlp-transformer": "0.207.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-4RQluMVVGMrHok/3SVeSJ6EnRNkA2MINcX88sh+d/7DjGUrewW/WT88IsMEci0wUM+5ykTpPPNbEOoW+jwHnbw=="],
|
||||
|
||||
"@opentelemetry/otlp-grpc-exporter-base": ["@opentelemetry/otlp-grpc-exporter-base@0.207.0", "", { "dependencies": { "@grpc/grpc-js": "^1.7.1", "@opentelemetry/core": "2.2.0", "@opentelemetry/otlp-exporter-base": "0.207.0", "@opentelemetry/otlp-transformer": "0.207.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-eKFjKNdsPed4q9yYqeI5gBTLjXxDM/8jwhiC0icw3zKxHVGBySoDsed5J5q/PGY/3quzenTr3FiTxA3NiNT+nw=="],
|
||||
|
||||
"@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.207.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.207.0", "@opentelemetry/core": "2.2.0", "@opentelemetry/resources": "2.2.0", "@opentelemetry/sdk-logs": "0.207.0", "@opentelemetry/sdk-metrics": "2.2.0", "@opentelemetry/sdk-trace-base": "2.2.0", "protobufjs": "^7.3.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-+6DRZLqM02uTIY5GASMZWUwr52sLfNiEe20+OEaZKhztCs3+2LxoTjb6JxFRd9q1qNqckXKYlUKjbH/AhG8/ZA=="],
|
||||
|
||||
"@opentelemetry/propagator-b3": ["@opentelemetry/propagator-b3@2.2.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-9CrbTLFi5Ee4uepxg2qlpQIozoJuoAZU5sKMx0Mn7Oh+p7UrgCiEV6C02FOxxdYVRRFQVCinYR8Kf6eMSQsIsw=="],
|
||||
|
||||
"@opentelemetry/propagator-jaeger": ["@opentelemetry/propagator-jaeger@2.2.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-FfeOHOrdhiNzecoB1jZKp2fybqmqMPJUXe2ZOydP7QzmTPYcfPeuaclTLYVhK3HyJf71kt8sTl92nV4YIaLaKA=="],
|
||||
|
||||
"@opentelemetry/resources": ["@opentelemetry/resources@2.2.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A=="],
|
||||
|
||||
"@opentelemetry/sdk-logs": ["@opentelemetry/sdk-logs@0.207.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.207.0", "@opentelemetry/core": "2.2.0", "@opentelemetry/resources": "2.2.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, "sha512-4MEQmn04y+WFe6cyzdrXf58hZxilvY59lzZj2AccuHW/+BxLn/rGVN/Irsi/F0qfBOpMOrrCLKTExoSL2zoQmg=="],
|
||||
|
||||
"@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.2.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/resources": "2.2.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-G5KYP6+VJMZzpGipQw7Giif48h6SGQ2PFKEYCybeXJsOCB4fp8azqMAAzE5lnnHK3ZVwYQrgmFbsUJO/zOnwGw=="],
|
||||
|
||||
"@opentelemetry/sdk-node": ["@opentelemetry/sdk-node@0.207.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.207.0", "@opentelemetry/core": "2.2.0", "@opentelemetry/exporter-logs-otlp-grpc": "0.207.0", "@opentelemetry/exporter-logs-otlp-http": "0.207.0", "@opentelemetry/exporter-logs-otlp-proto": "0.207.0", "@opentelemetry/exporter-metrics-otlp-grpc": "0.207.0", "@opentelemetry/exporter-metrics-otlp-http": "0.207.0", "@opentelemetry/exporter-metrics-otlp-proto": "0.207.0", "@opentelemetry/exporter-prometheus": "0.207.0", "@opentelemetry/exporter-trace-otlp-grpc": "0.207.0", "@opentelemetry/exporter-trace-otlp-http": "0.207.0", "@opentelemetry/exporter-trace-otlp-proto": "0.207.0", "@opentelemetry/exporter-zipkin": "2.2.0", "@opentelemetry/instrumentation": "0.207.0", "@opentelemetry/propagator-b3": "2.2.0", "@opentelemetry/propagator-jaeger": "2.2.0", "@opentelemetry/resources": "2.2.0", "@opentelemetry/sdk-logs": "0.207.0", "@opentelemetry/sdk-metrics": "2.2.0", "@opentelemetry/sdk-trace-base": "2.2.0", "@opentelemetry/sdk-trace-node": "2.2.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-hnRsX/M8uj0WaXOBvFenQ8XsE8FLVh2uSnn1rkWu4mx+qu7EKGUZvZng6y/95cyzsqOfiaDDr08Ek4jppkIDNg=="],
|
||||
|
||||
"@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.5.0", "", { "dependencies": { "@opentelemetry/core": "2.5.0", "@opentelemetry/resources": "2.5.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-VzRf8LzotASEyNDUxTdaJ9IRJ1/h692WyArDBInf5puLCjxbICD6XkHgpuudis56EndyS7LYFmtTMny6UABNdQ=="],
|
||||
|
||||
"@opentelemetry/sdk-trace-node": ["@opentelemetry/sdk-trace-node@2.2.0", "", { "dependencies": { "@opentelemetry/context-async-hooks": "2.2.0", "@opentelemetry/core": "2.2.0", "@opentelemetry/sdk-trace-base": "2.2.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-+OaRja3f0IqGG2kptVeYsrZQK9nKRSpfFrKtRBq4uh6nIB8bTBgaGvYQrQoRrQWQMA5dK5yLhDMDc0dvYvCOIQ=="],
|
||||
|
||||
"@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.39.0", "", {}, "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg=="],
|
||||
|
||||
"@protobufjs/aspromise": ["@protobufjs/aspromise@1.1.2", "", {}, "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="],
|
||||
|
||||
"@protobufjs/base64": ["@protobufjs/base64@1.1.2", "", {}, "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="],
|
||||
|
||||
"@protobufjs/codegen": ["@protobufjs/codegen@2.0.4", "", {}, "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg=="],
|
||||
|
||||
"@protobufjs/eventemitter": ["@protobufjs/eventemitter@1.1.0", "", {}, "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q=="],
|
||||
|
||||
"@protobufjs/fetch": ["@protobufjs/fetch@1.1.0", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.1", "@protobufjs/inquire": "^1.1.0" } }, "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ=="],
|
||||
|
||||
"@protobufjs/float": ["@protobufjs/float@1.0.2", "", {}, "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ=="],
|
||||
|
||||
"@protobufjs/inquire": ["@protobufjs/inquire@1.1.0", "", {}, "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q=="],
|
||||
|
||||
"@protobufjs/path": ["@protobufjs/path@1.1.2", "", {}, "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA=="],
|
||||
|
||||
"@protobufjs/pool": ["@protobufjs/pool@1.1.0", "", {}, "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw=="],
|
||||
|
||||
"@protobufjs/utf8": ["@protobufjs/utf8@1.1.0", "", {}, "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="],
|
||||
|
||||
"@sapphire/async-queue": ["@sapphire/async-queue@1.5.5", "", {}, "sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg=="],
|
||||
|
||||
"@sapphire/shapeshift": ["@sapphire/shapeshift@4.0.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "lodash": "^4.17.21" } }, "sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg=="],
|
||||
|
||||
"@sapphire/snowflake": ["@sapphire/snowflake@3.5.3", "", {}, "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ=="],
|
||||
|
||||
"@smithy/abort-controller": ["@smithy/abort-controller@4.2.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-peuVfkYHAmS5ybKxWcfraK7WBBP0J+rkfUcbHJJKQ4ir3UAUNQI+Y4Vt/PqSzGqgloJ5O1dk7+WzNL8wcCSXbw=="],
|
||||
|
||||
"@smithy/chunked-blob-reader": ["@smithy/chunked-blob-reader@5.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-WmU0TnhEAJLWvfSeMxBNe5xtbselEO8+4wG0NtZeL8oR21WgH1xiO37El+/Y+H/Ie4SCwBy3MxYWmOYaGgZueA=="],
|
||||
|
||||
"@smithy/chunked-blob-reader-native": ["@smithy/chunked-blob-reader-native@4.2.1", "", { "dependencies": { "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-lX9Ay+6LisTfpLid2zZtIhSEjHMZoAR5hHCR4H7tBz/Zkfr5ea8RcQ7Tk4mi0P76p4cN+Btz16Ffno7YHpKXnQ=="],
|
||||
|
||||
"@smithy/config-resolver": ["@smithy/config-resolver@4.4.6", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", "@smithy/util-config-provider": "^4.2.0", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "tslib": "^2.6.2" } }, "sha512-qJpzYC64kaj3S0fueiu3kXm8xPrR3PcXDPEgnaNMRn0EjNSZFoFjvbUp0YUDsRhN1CB90EnHJtbxWKevnH99UQ=="],
|
||||
|
||||
"@smithy/core": ["@smithy/core@3.23.0", "", { "dependencies": { "@smithy/middleware-serde": "^4.2.9", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-stream": "^4.5.12", "@smithy/util-utf8": "^4.2.0", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-Yq4UPVoQICM9zHnByLmG8632t2M0+yap4T7ANVw482J0W7HW0pOuxwVmeOwzJqX2Q89fkXz0Vybz55Wj2Xzrsg=="],
|
||||
|
||||
"@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.8", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "tslib": "^2.6.2" } }, "sha512-FNT0xHS1c/CPN8upqbMFP83+ul5YgdisfCfkZ86Jh2NSmnqw/AJ6x5pEogVCTVvSm7j9MopRU89bmDelxuDMYw=="],
|
||||
|
||||
"@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.8", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.12.0", "@smithy/util-hex-encoding": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-jS/O5Q14UsufqoGhov7dHLOPCzkYJl9QDzusI2Psh4wyYx/izhzvX9P4D69aTxcdfVhEPhjK+wYyn/PzLjKbbw=="],
|
||||
|
||||
"@smithy/eventstream-serde-browser": ["@smithy/eventstream-serde-browser@4.2.8", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-MTfQT/CRQz5g24ayXdjg53V0mhucZth4PESoA5IhvaWVDTOQLfo8qI9vzqHcPsdd2v6sqfTYqF5L/l+pea5Uyw=="],
|
||||
|
||||
"@smithy/eventstream-serde-config-resolver": ["@smithy/eventstream-serde-config-resolver@4.3.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-ah12+luBiDGzBruhu3efNy1IlbwSEdNiw8fOZksoKoWW1ZHvO/04MQsdnws/9Aj+5b0YXSSN2JXKy/ClIsW8MQ=="],
|
||||
|
||||
"@smithy/eventstream-serde-node": ["@smithy/eventstream-serde-node@4.2.8", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-cYpCpp29z6EJHa5T9WL0KAlq3SOKUQkcgSoeRfRVwjGgSFl7Uh32eYGt7IDYCX20skiEdRffyDpvF2efEZPC0A=="],
|
||||
|
||||
"@smithy/eventstream-serde-universal": ["@smithy/eventstream-serde-universal@4.2.8", "", { "dependencies": { "@smithy/eventstream-codec": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-iJ6YNJd0bntJYnX6s52NC4WFYcZeKrPUr1Kmmr5AwZcwCSzVpS7oavAmxMR7pMq7V+D1G4s9F5NJK0xwOsKAlQ=="],
|
||||
|
||||
"@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.9", "", { "dependencies": { "@smithy/protocol-http": "^5.3.8", "@smithy/querystring-builder": "^4.2.8", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-I4UhmcTYXBrct03rwzQX1Y/iqQlzVQaPxWjCjula++5EmWq9YGBrx6bbGqluGc1f0XEfhSkiY4jhLgbsJUMKRA=="],
|
||||
|
||||
"@smithy/hash-blob-browser": ["@smithy/hash-blob-browser@4.2.9", "", { "dependencies": { "@smithy/chunked-blob-reader": "^5.2.0", "@smithy/chunked-blob-reader-native": "^4.2.1", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-m80d/iicI7DlBDxyQP6Th7BW/ejDGiF0bgI754+tiwK0lgMkcaIBgvwwVc7OFbY4eUzpGtnig52MhPAEJ7iNYg=="],
|
||||
|
||||
"@smithy/hash-node": ["@smithy/hash-node@4.2.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-7ZIlPbmaDGxVoxErDZnuFG18WekhbA/g2/i97wGj+wUBeS6pcUeAym8u4BXh/75RXWhgIJhyC11hBzig6MljwA=="],
|
||||
|
||||
"@smithy/hash-stream-node": ["@smithy/hash-stream-node@4.2.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-v0FLTXgHrTeheYZFGhR+ehX5qUm4IQsjAiL9qehad2cyjMWcN2QG6/4mSwbSgEQzI7jwfoXj7z4fxZUx/Mhj2w=="],
|
||||
|
||||
"@smithy/invalid-dependency": ["@smithy/invalid-dependency@4.2.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-N9iozRybwAQ2dn9Fot9kI6/w9vos2oTXLhtK7ovGqwZjlOcxu6XhPlpLpC+INsxktqHinn5gS2DXDjDF2kG5sQ=="],
|
||||
|
||||
"@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="],
|
||||
|
||||
"@smithy/md5-js": ["@smithy/md5-js@4.2.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-oGMaLj4tVZzLi3itBa9TCswgMBr7k9b+qKYowQ6x1rTyTuO1IU2YHdHUa+891OsOH+wCsH7aTPRsTJO3RMQmjQ=="],
|
||||
|
||||
"@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.2.8", "", { "dependencies": { "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-RO0jeoaYAB1qBRhfVyq0pMgBoUK34YEJxVxyjOWYZiOKOq2yMZ4MnVXMZCUDenpozHue207+9P5ilTV1zeda0A=="],
|
||||
|
||||
"@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.4.14", "", { "dependencies": { "@smithy/core": "^3.23.0", "@smithy/middleware-serde": "^4.2.9", "@smithy/node-config-provider": "^4.3.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-middleware": "^4.2.8", "tslib": "^2.6.2" } }, "sha512-FUFNE5KVeaY6U/GL0nzAAHkaCHzXLZcY1EhtQnsAqhD8Du13oPKtMB9/0WK4/LK6a/T5OZ24wPoSShff5iI6Ag=="],
|
||||
|
||||
"@smithy/middleware-retry": ["@smithy/middleware-retry@4.4.31", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.8", "@smithy/protocol-http": "^5.3.8", "@smithy/service-error-classification": "^4.2.8", "@smithy/smithy-client": "^4.11.3", "@smithy/types": "^4.12.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-RXBzLpMkIrxBPe4C8OmEOHvS8aH9RUuCOH++Acb5jZDEblxDjyg6un72X9IcbrGTJoiUwmI7hLypNfuDACypbg=="],
|
||||
|
||||
"@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.9", "", { "dependencies": { "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-eMNiej0u/snzDvlqRGSN3Vl0ESn3838+nKyVfF2FKNXFbi4SERYT6PR392D39iczngbqqGG0Jl1DlCnp7tBbXQ=="],
|
||||
|
||||
"@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-w6LCfOviTYQjBctOKSwy6A8FIkQy7ICvglrZFl6Bw4FmcQ1Z420fUtIhxaUZZshRe0VCq4kvDiPiXrPZAe8oRA=="],
|
||||
|
||||
"@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.8", "", { "dependencies": { "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-aFP1ai4lrbVlWjfpAfRSL8KFcnJQYfTl5QxLJXY32vghJrDuFyPZ6LtUL+JEGYiFRG1PfPLHLoxj107ulncLIg=="],
|
||||
|
||||
"@smithy/node-http-handler": ["@smithy/node-http-handler@4.4.10", "", { "dependencies": { "@smithy/abort-controller": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/querystring-builder": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-u4YeUwOWRZaHbWaebvrs3UhwQwj+2VNmcVCwXcYTvPIuVyM7Ex1ftAj+fdbG/P4AkBwLq/+SKn+ydOI4ZJE9PA=="],
|
||||
|
||||
"@smithy/property-provider": ["@smithy/property-provider@4.2.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-EtCTbyIveCKeOXDSWSdze3k612yCPq1YbXsbqX3UHhkOSW8zKsM9NOJG5gTIya0vbY2DIaieG8pKo1rITHYL0w=="],
|
||||
|
||||
"@smithy/protocol-http": ["@smithy/protocol-http@5.3.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-QNINVDhxpZ5QnP3aviNHQFlRogQZDfYlCkQT+7tJnErPQbDhysondEjhikuANxgMsZrkGeiAxXy4jguEGsDrWQ=="],
|
||||
|
||||
"@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "@smithy/util-uri-escape": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-Xr83r31+DrE8CP3MqPgMJl+pQlLLmOfiEUnoyAlGzzJIrEsbKsPy1hqH0qySaQm4oWrCBlUqRt+idEgunKB+iw=="],
|
||||
|
||||
"@smithy/querystring-parser": ["@smithy/querystring-parser@4.2.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-vUurovluVy50CUlazOiXkPq40KGvGWSdmusa3130MwrR1UNnNgKAlj58wlOe61XSHRpUfIIh6cE0zZ8mzKaDPA=="],
|
||||
|
||||
"@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.8", "", { "dependencies": { "@smithy/types": "^4.12.0" } }, "sha512-mZ5xddodpJhEt3RkCjbmUQuXUOaPNTkbMGR0bcS8FE0bJDLMZlhmpgrvPNCYglVw5rsYTpSnv19womw9WWXKQQ=="],
|
||||
|
||||
"@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.3", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-DfQjxXQnzC5UbCUPeC3Ie8u+rIWZTvuDPAGU/BxzrOGhRvgUanaP68kDZA+jaT3ZI+djOf+4dERGlm9mWfFDrg=="],
|
||||
|
||||
"@smithy/signature-v4": ["@smithy/signature-v4@5.3.8", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-uri-escape": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-6A4vdGj7qKNRF16UIcO8HhHjKW27thsxYci+5r/uVRkdcBEkOEiY8OMPuydLX4QHSrJqGHPJzPRwwVTqbLZJhg=="],
|
||||
|
||||
"@smithy/smithy-client": ["@smithy/smithy-client@4.11.3", "", { "dependencies": { "@smithy/core": "^3.23.0", "@smithy/middleware-endpoint": "^4.4.14", "@smithy/middleware-stack": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "@smithy/util-stream": "^4.5.12", "tslib": "^2.6.2" } }, "sha512-Q7kY5sDau8OoE6Y9zJoRGgje8P4/UY0WzH8R2ok0PDh+iJ+ZnEKowhjEqYafVcubkbYxQVaqwm3iufktzhprGg=="],
|
||||
|
||||
"@smithy/types": ["@smithy/types@4.12.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw=="],
|
||||
|
||||
"@smithy/url-parser": ["@smithy/url-parser@4.2.8", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-NQho9U68TGMEU639YkXnVMV3GEFFULmmaWdlu1E9qzyIePOHsoSnagTGSDv1Zi8DCNN6btxOSdgmy5E/hsZwhA=="],
|
||||
|
||||
"@smithy/util-base64": ["@smithy/util-base64@4.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ=="],
|
||||
|
||||
"@smithy/util-body-length-browser": ["@smithy/util-body-length-browser@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg=="],
|
||||
|
||||
"@smithy/util-body-length-node": ["@smithy/util-body-length-node@4.2.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA=="],
|
||||
|
||||
"@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="],
|
||||
|
||||
"@smithy/util-config-provider": ["@smithy/util-config-provider@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q=="],
|
||||
|
||||
"@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.30", "", { "dependencies": { "@smithy/property-provider": "^4.2.8", "@smithy/smithy-client": "^4.11.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-cMni0uVU27zxOiU8TuC8pQLC1pYeZ/xEMxvchSK/ILwleRd1ugobOcIRr5vXtcRqKd4aBLWlpeBoDPJJ91LQng=="],
|
||||
|
||||
"@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.33", "", { "dependencies": { "@smithy/config-resolver": "^4.4.6", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/smithy-client": "^4.11.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-LEb2aq5F4oZUSzWBG7S53d4UytZSkOEJPXcBq/xbG2/TmK9EW5naUZ8lKu1BEyWMzdHIzEVN16M3k8oxDq+DJA=="],
|
||||
|
||||
"@smithy/util-endpoints": ["@smithy/util-endpoints@3.2.8", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-8JaVTn3pBDkhZgHQ8R0epwWt+BqPSLCjdjXXusK1onwJlRuN69fbvSK66aIKKO7SwVFM6x2J2ox5X8pOaWcUEw=="],
|
||||
|
||||
"@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="],
|
||||
|
||||
"@smithy/util-middleware": ["@smithy/util-middleware@4.2.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-PMqfeJxLcNPMDgvPbbLl/2Vpin+luxqTGPpW3NAQVLbRrFRzTa4rNAASYeIGjRV9Ytuhzny39SpyU04EQreF+A=="],
|
||||
|
||||
"@smithy/util-retry": ["@smithy/util-retry@4.2.8", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-CfJqwvoRY0kTGe5AkQokpURNCT1u/MkRzMTASWMPPo2hNSnKtF1D45dQl3DE2LKLr4m+PW9mCeBMJr5mCAVThg=="],
|
||||
|
||||
"@smithy/util-stream": ["@smithy/util-stream@4.5.12", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.9", "@smithy/node-http-handler": "^4.4.10", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-D8tgkrmhAX/UNeCZbqbEO3uqyghUnEmmoO9YEvRuwxjlkKKUE7FOgCJnqpTlQPe9MApdWPky58mNQQHbnCzoNg=="],
|
||||
|
||||
"@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA=="],
|
||||
|
||||
"@smithy/util-utf8": ["@smithy/util-utf8@4.2.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw=="],
|
||||
|
||||
"@smithy/util-waiter": ["@smithy/util-waiter@4.2.8", "", { "dependencies": { "@smithy/abort-controller": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-n+lahlMWk+aejGuax7DPWtqav8HYnWxQwR+LCG2BgCUmaGcTe9qZCFsmw8TMg9iG75HOwhrJCX9TCJRLH+Yzqg=="],
|
||||
|
||||
"@smithy/uuid": ["@smithy/uuid@1.1.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw=="],
|
||||
|
||||
"@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||
|
||||
"@types/bun": ["@types/bun@1.3.9", "", { "dependencies": { "bun-types": "1.3.9" } }, "sha512-KQ571yULOdWJiMH+RIWIOZ7B2RXQGpL1YQrBtLIV3FqDcCu6FsbFUBwhdKUlCKUpS3PJDsHlJ1QKlpxoVR+xtw=="],
|
||||
|
||||
"@types/node": ["@types/node@22.19.11", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-BH7YwL6rA93ReqeQS1c4bsPpcfOmJasG+Fkr6Y59q83f9M1WcBRHR2vM+P9eOisYRcN3ujQoiZY8uk5W+1WL8w=="],
|
||||
|
||||
"@types/pg": ["@types/pg@8.11.6", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^4.0.1" } }, "sha512-/2WmmBXHLsfRqzfHW7BNZ8SbYzE8OSk7i3WjFYvfgRHj7S1xj+16Je5fUKv3lVdVzk/zn9TXOqf+avFCFIE0yQ=="],
|
||||
|
||||
"@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="],
|
||||
|
||||
"@vladfrangu/async_event_emitter": ["@vladfrangu/async_event_emitter@2.4.7", "", {}, "sha512-Xfe6rpCTxSxfbswi/W/Pz7zp1WWSNn4A0eW4mLkQUewCrXXtMj31lCg+iQyTkh/CkusZSq9eDflu7tjEDXUY6g=="],
|
||||
|
||||
"acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
|
||||
|
||||
"acorn-import-attributes": ["acorn-import-attributes@1.9.5", "", { "peerDependencies": { "acorn": "^8" } }, "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ=="],
|
||||
|
||||
"ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
||||
|
||||
"asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],
|
||||
|
||||
"axios": ["axios@1.13.5", "", { "dependencies": { "follow-redirects": "^1.15.11", "form-data": "^4.0.5", "proxy-from-env": "^1.1.0" } }, "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q=="],
|
||||
|
||||
"base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
|
||||
|
||||
"bowser": ["bowser@2.14.1", "", {}, "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg=="],
|
||||
|
||||
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
|
||||
|
||||
"buffer": ["buffer@5.6.0", "", { "dependencies": { "base64-js": "^1.0.2", "ieee754": "^1.1.4" } }, "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw=="],
|
||||
|
||||
"bun-types": ["bun-types@1.3.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-+UBWWOakIP4Tswh0Bt0QD0alpTY8cb5hvgiYeWCMet9YukHbzuruIEeXC2D7nMJPB12kbh8C7XJykSexEqGKJg=="],
|
||||
|
||||
"busboy": ["busboy@1.6.0", "", { "dependencies": { "streamsearch": "^1.1.0" } }, "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA=="],
|
||||
|
||||
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
|
||||
|
||||
"chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="],
|
||||
|
||||
"cjs-module-lexer": ["cjs-module-lexer@2.2.0", "", {}, "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ=="],
|
||||
|
||||
"cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
|
||||
|
||||
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
|
||||
|
||||
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
|
||||
|
||||
"combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="],
|
||||
|
||||
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="],
|
||||
|
||||
"discord-api-types": ["discord-api-types@0.38.38", "", {}, "sha512-7qcM5IeZrfb+LXW07HvoI5L+j4PQeMZXEkSm1htHAHh4Y9JSMXBWjy/r7zmUCOj4F7zNjMcm7IMWr131MT2h0Q=="],
|
||||
|
||||
"discord.js": ["discord.js@14.25.1", "", { "dependencies": { "@discordjs/builders": "^1.13.0", "@discordjs/collection": "1.5.3", "@discordjs/formatters": "^0.6.2", "@discordjs/rest": "^2.6.0", "@discordjs/util": "^1.2.0", "@discordjs/ws": "^1.2.3", "@sapphire/snowflake": "3.5.3", "discord-api-types": "^0.38.33", "fast-deep-equal": "3.1.3", "lodash.snakecase": "4.1.1", "magic-bytes.js": "^1.10.0", "tslib": "^2.6.3", "undici": "6.21.3" } }, "sha512-2l0gsPOLPs5t6GFZfQZKnL1OJNYFcuC/ETWsW4VtKVD/tg4ICa9x+jb9bkPffkMdRpRpuUaO/fKkHCBeiCKh8g=="],
|
||||
|
||||
"dotenv": ["dotenv@17.2.4", "", {}, "sha512-mudtfb4zRB4bVvdj0xRo+e6duH1csJRM8IukBqfTRvHotn9+LBXB8ynAidP9zHqoRC/fsllXgk4kCKlR21fIhw=="],
|
||||
|
||||
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
|
||||
|
||||
"effect": ["effect@3.19.16", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-7+XC3vGrbAhCHd8LTFHvnZjRpZKZ8YHRZqJTkpNoxcJ2mCyNs2SwI+6VkV/ij8Y3YW7wfBN4EbU06/F5+m/wkQ=="],
|
||||
|
||||
"emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||
|
||||
"es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
|
||||
|
||||
"es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
|
||||
|
||||
"es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="],
|
||||
|
||||
"es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="],
|
||||
|
||||
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
|
||||
|
||||
"events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="],
|
||||
|
||||
"expand-tilde": ["expand-tilde@2.0.2", "", { "dependencies": { "homedir-polyfill": "^1.0.1" } }, "sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw=="],
|
||||
|
||||
"fast-check": ["fast-check@3.23.2", "", { "dependencies": { "pure-rand": "^6.1.0" } }, "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A=="],
|
||||
|
||||
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
|
||||
|
||||
"fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
|
||||
|
||||
"fast-xml-parser": ["fast-xml-parser@5.3.4", "", { "dependencies": { "strnum": "^2.1.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-EFd6afGmXlCx8H8WTZHhAoDaWaGyuIBoZJ2mknrNxug+aZKjkp0a0dlars9Izl+jF+7Gu1/5f/2h68cQpe0IiA=="],
|
||||
|
||||
"fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="],
|
||||
|
||||
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
|
||||
|
||||
"follow-redirects": ["follow-redirects@1.15.11", "", {}, "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ=="],
|
||||
|
||||
"form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="],
|
||||
|
||||
"forwarded-parse": ["forwarded-parse@2.1.2", "", {}, "sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw=="],
|
||||
|
||||
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
|
||||
|
||||
"get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
|
||||
|
||||
"get-intrinsic": ["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" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
|
||||
|
||||
"get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
|
||||
|
||||
"glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
||||
|
||||
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
|
||||
|
||||
"has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
|
||||
|
||||
"has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="],
|
||||
|
||||
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
|
||||
|
||||
"homedir-polyfill": ["homedir-polyfill@1.0.3", "", { "dependencies": { "parse-passwd": "^1.0.0" } }, "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA=="],
|
||||
|
||||
"ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
|
||||
|
||||
"import-in-the-middle": ["import-in-the-middle@2.0.6", "", { "dependencies": { "acorn": "^8.15.0", "acorn-import-attributes": "^1.9.5", "cjs-module-lexer": "^2.2.0", "module-details-from-path": "^1.0.4" } }, "sha512-3vZV3jX0XRFW3EJDTwzWoZa+RH1b8eTTx6YOCjglrLyPuepwoBti1k3L2dKwdCUrnVEfc5CuRuGstaC/uQJJaw=="],
|
||||
|
||||
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
|
||||
|
||||
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
|
||||
|
||||
"is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
|
||||
|
||||
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
|
||||
|
||||
"is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
|
||||
|
||||
"isomorphic-ws": ["isomorphic-ws@5.0.0", "", { "peerDependencies": { "ws": "*" } }, "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw=="],
|
||||
|
||||
"lodash": ["lodash@4.17.23", "", {}, "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w=="],
|
||||
|
||||
"lodash.camelcase": ["lodash.camelcase@4.3.0", "", {}, "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA=="],
|
||||
|
||||
"lodash.snakecase": ["lodash.snakecase@4.1.1", "", {}, "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw=="],
|
||||
|
||||
"long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="],
|
||||
|
||||
"magic-bytes.js": ["magic-bytes.js@1.13.0", "", {}, "sha512-afO2mnxW7GDTXMm5/AoN1WuOcdoKhtgXjIvHmobqTD1grNplhGdv3PFOyjCVmrnOZBIT/gD/koDKpYG+0mvHcg=="],
|
||||
|
||||
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
|
||||
|
||||
"merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
|
||||
|
||||
"micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
|
||||
|
||||
"mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
|
||||
|
||||
"mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
|
||||
|
||||
"minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
|
||||
|
||||
"minizlib": ["minizlib@3.1.0", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw=="],
|
||||
|
||||
"module-details-from-path": ["module-details-from-path@1.0.4", "", {}, "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w=="],
|
||||
|
||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||
|
||||
"obuf": ["obuf@1.1.2", "", {}, "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg=="],
|
||||
|
||||
"parse-passwd": ["parse-passwd@1.0.0", "", {}, "sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q=="],
|
||||
|
||||
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
||||
|
||||
"pg-int8": ["pg-int8@1.0.1", "", {}, "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw=="],
|
||||
|
||||
"pg-numeric": ["pg-numeric@1.0.2", "", {}, "sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw=="],
|
||||
|
||||
"pg-protocol": ["pg-protocol@1.11.0", "", {}, "sha512-pfsxk2M9M3BuGgDOfuy37VNRRX3jmKgMjcvAcWqNDpZSf4cUmv8HSOl5ViRQFsfARFn0KuUQTgLxVMbNq5NW3g=="],
|
||||
|
||||
"pg-types": ["pg-types@4.1.0", "", { "dependencies": { "pg-int8": "1.0.1", "pg-numeric": "1.0.2", "postgres-array": "~3.0.1", "postgres-bytea": "~3.0.0", "postgres-date": "~2.1.0", "postgres-interval": "^3.0.0", "postgres-range": "^1.1.1" } }, "sha512-o2XFanIMy/3+mThw69O8d4n1E5zsLhdO+OPqswezu7Z5ekP4hYDqlDjlmOpYMbzY2Br0ufCwJLdDIXeNVwcWFg=="],
|
||||
|
||||
"picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||
|
||||
"postgres-array": ["postgres-array@3.0.4", "", {}, "sha512-nAUSGfSDGOaOAEGwqsRY27GPOea7CNipJPOA7lPbdEpx5Kg3qzdP0AaWC5MlhTWV9s4hFX39nomVZ+C4tnGOJQ=="],
|
||||
|
||||
"postgres-bytea": ["postgres-bytea@3.0.0", "", { "dependencies": { "obuf": "~1.1.2" } }, "sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw=="],
|
||||
|
||||
"postgres-date": ["postgres-date@2.1.0", "", {}, "sha512-K7Juri8gtgXVcDfZttFKVmhglp7epKb1K4pgrkLxehjqkrgPhfG6OO8LHLkfaqkbpjNRnra018XwAr1yQFWGcA=="],
|
||||
|
||||
"postgres-interval": ["postgres-interval@3.0.0", "", {}, "sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw=="],
|
||||
|
||||
"postgres-range": ["postgres-range@1.1.4", "", {}, "sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w=="],
|
||||
|
||||
"protobufjs": ["protobufjs@7.5.4", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg=="],
|
||||
|
||||
"proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="],
|
||||
|
||||
"pure-rand": ["pure-rand@6.1.0", "", {}, "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA=="],
|
||||
|
||||
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
|
||||
|
||||
"readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
|
||||
|
||||
"require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
|
||||
|
||||
"require-in-the-middle": ["require-in-the-middle@8.0.1", "", { "dependencies": { "debug": "^4.3.5", "module-details-from-path": "^1.0.3" } }, "sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ=="],
|
||||
|
||||
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
|
||||
|
||||
"run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
|
||||
|
||||
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
|
||||
|
||||
"shell-quote": ["shell-quote@1.8.3", "", {}, "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw=="],
|
||||
|
||||
"stream-browserify": ["stream-browserify@3.0.0", "", { "dependencies": { "inherits": "~2.0.4", "readable-stream": "^3.5.0" } }, "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA=="],
|
||||
|
||||
"streamsearch": ["streamsearch@1.1.0", "", {}, "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg=="],
|
||||
|
||||
"string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||
|
||||
"string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="],
|
||||
|
||||
"strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"strnum": ["strnum@2.1.2", "", {}, "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ=="],
|
||||
|
||||
"tar": ["tar@7.5.7", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ=="],
|
||||
|
||||
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
|
||||
|
||||
"ts-mixer": ["ts-mixer@6.0.4", "", {}, "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA=="],
|
||||
|
||||
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||
|
||||
"undici": ["undici@6.21.3", "", {}, "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw=="],
|
||||
|
||||
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
|
||||
|
||||
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
|
||||
|
||||
"wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
|
||||
|
||||
"ws": ["ws@8.19.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg=="],
|
||||
|
||||
"y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
|
||||
|
||||
"yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="],
|
||||
|
||||
"yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="],
|
||||
|
||||
"yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
|
||||
|
||||
"zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
||||
|
||||
"@aws-crypto/sha1-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
|
||||
|
||||
"@aws-crypto/sha256-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
|
||||
|
||||
"@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
|
||||
|
||||
"@discordjs/rest/@discordjs/collection": ["@discordjs/collection@2.1.1", "", {}, "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg=="],
|
||||
|
||||
"@discordjs/ws/@discordjs/collection": ["@discordjs/collection@2.1.1", "", {}, "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg=="],
|
||||
|
||||
"@opentelemetry/exporter-logs-otlp-proto/@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.2.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/resources": "2.2.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-xWQgL0Bmctsalg6PaXExmzdedSp3gyKV8mQBwK/j9VGdCDu2fmXIb2gAehBKbkXCpJ4HPkgv3QfoJWRT4dHWbw=="],
|
||||
|
||||
"@opentelemetry/exporter-trace-otlp-grpc/@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.2.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/resources": "2.2.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-xWQgL0Bmctsalg6PaXExmzdedSp3gyKV8mQBwK/j9VGdCDu2fmXIb2gAehBKbkXCpJ4HPkgv3QfoJWRT4dHWbw=="],
|
||||
|
||||
"@opentelemetry/exporter-trace-otlp-http/@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.2.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/resources": "2.2.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-xWQgL0Bmctsalg6PaXExmzdedSp3gyKV8mQBwK/j9VGdCDu2fmXIb2gAehBKbkXCpJ4HPkgv3QfoJWRT4dHWbw=="],
|
||||
|
||||
"@opentelemetry/exporter-trace-otlp-proto/@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.2.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/resources": "2.2.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-xWQgL0Bmctsalg6PaXExmzdedSp3gyKV8mQBwK/j9VGdCDu2fmXIb2gAehBKbkXCpJ4HPkgv3QfoJWRT4dHWbw=="],
|
||||
|
||||
"@opentelemetry/exporter-zipkin/@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.2.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/resources": "2.2.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-xWQgL0Bmctsalg6PaXExmzdedSp3gyKV8mQBwK/j9VGdCDu2fmXIb2gAehBKbkXCpJ4HPkgv3QfoJWRT4dHWbw=="],
|
||||
|
||||
"@opentelemetry/otlp-transformer/@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.2.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/resources": "2.2.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-xWQgL0Bmctsalg6PaXExmzdedSp3gyKV8mQBwK/j9VGdCDu2fmXIb2gAehBKbkXCpJ4HPkgv3QfoJWRT4dHWbw=="],
|
||||
|
||||
"@opentelemetry/sdk-node/@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.2.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/resources": "2.2.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-xWQgL0Bmctsalg6PaXExmzdedSp3gyKV8mQBwK/j9VGdCDu2fmXIb2gAehBKbkXCpJ4HPkgv3QfoJWRT4dHWbw=="],
|
||||
|
||||
"@opentelemetry/sdk-trace-base/@opentelemetry/core": ["@opentelemetry/core@2.5.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ=="],
|
||||
|
||||
"@opentelemetry/sdk-trace-base/@opentelemetry/resources": ["@opentelemetry/resources@2.5.0", "", { "dependencies": { "@opentelemetry/core": "2.5.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-F8W52ApePshpoSrfsSk1H2yJn9aKjCrbpQF1M9Qii0GHzbfVeFUB+rc3X4aggyZD8x9Gu3Slua+s6krmq6Dt8g=="],
|
||||
|
||||
"@opentelemetry/sdk-trace-node/@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.2.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/resources": "2.2.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-xWQgL0Bmctsalg6PaXExmzdedSp3gyKV8mQBwK/j9VGdCDu2fmXIb2gAehBKbkXCpJ4HPkgv3QfoJWRT4dHWbw=="],
|
||||
|
||||
"@aws-crypto/sha1-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
|
||||
|
||||
"@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
|
||||
|
||||
"@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
|
||||
|
||||
"@aws-crypto/sha1-browser/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="],
|
||||
|
||||
"@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="],
|
||||
|
||||
"@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="],
|
||||
}
|
||||
}
|
||||
28
packages/discord/package.json
Normal file
28
packages/discord/package.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "discord-bot",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"db:init": "bun run src/db/init.ts",
|
||||
"dev": "bun run --watch src/index.ts",
|
||||
"dev:setup": "bun run db:init",
|
||||
"start": "bun run src/index.ts",
|
||||
"build": "bun build src/index.ts --target=bun --outdir=dist",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"check": "bun run typecheck && bun run build"
|
||||
},
|
||||
"dependencies": {
|
||||
"discord.js": "^14",
|
||||
"@neondatabase/serverless": "^0.10",
|
||||
"@daytonaio/sdk": "latest",
|
||||
"@opencode-ai/sdk": "latest",
|
||||
"effect": "^3",
|
||||
"zod": "^3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest",
|
||||
"@types/node": "^22",
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
||||
28
packages/discord/src/agent-prompt.md
Normal file
28
packages/discord/src/agent-prompt.md
Normal file
@@ -0,0 +1,28 @@
|
||||
You're a senior engineer on the OpenCode team. You're in a Discord channel where teammates and community members ask questions about the codebase. You have the full opencode repo cloned at your working directory.
|
||||
|
||||
This is an internal tool — people tag you to ask about how things work, where code lives, why something was built a certain way, or to get help debugging. Think of it like someone pinging you on Slack.
|
||||
|
||||
## Tone
|
||||
|
||||
- Just answer the question. Don't preface with "Based on my analysis" or "I'd be happy to help" or "Let me look into that for you." Just give the answer.
|
||||
- Write like you're messaging a coworker. Lowercase is fine. Short paragraphs. No essays.
|
||||
- Don't over-format. Use markdown for code blocks and the occasional list, but don't turn every response into a formatted document with headers and bullet points. Just talk.
|
||||
- Be direct and opinionated when it makes sense. "yeah that's a bug" or "I'd just use X here" is better than hedging everything.
|
||||
- If you don't know, say "not sure" or "I'd have to dig into that more." Don't make stuff up.
|
||||
- Match the vibe. Quick question = quick answer. Detailed question = longer answer with code refs.
|
||||
|
||||
## What you do
|
||||
|
||||
- Search and read the codebase to answer questions
|
||||
- Run git, grep, gh CLI to find things
|
||||
- Reference specific files and line numbers like `src/tui/app.ts:142`
|
||||
- Quote relevant code when it helps
|
||||
- Explain architecture and design decisions based on what's actually in the code
|
||||
|
||||
## Rules
|
||||
|
||||
- **Search the code first.** Don't answer from memory — look it up and cite where things are.
|
||||
- **Don't edit files unless someone explicitly asks you to.**
|
||||
- **Keep it short.** Under 1500 chars unless the question actually needs a longer answer.
|
||||
- **Summarize command output.** Don't paste raw terminal dumps.
|
||||
- When you reference code, include the file path so people can go look at it.
|
||||
56
packages/discord/src/config.ts
Normal file
56
packages/discord/src/config.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { z } from "zod";
|
||||
|
||||
const envSchema = z.object({
|
||||
DISCORD_TOKEN: z.string().min(1),
|
||||
ALLOWED_CHANNEL_IDS: z.string().default("").transform((s) =>
|
||||
s
|
||||
.split(",")
|
||||
.map((id) => id.trim())
|
||||
.filter((id) => id.length > 0),
|
||||
),
|
||||
DISCORD_CATEGORY_ID: z.string().default(""),
|
||||
DISCORD_ROLE_ID: z.string().default(""),
|
||||
DISCORD_REQUIRED_ROLE_ID: z.string().default(""),
|
||||
DATABASE_URL: z.string().min(1),
|
||||
DAYTONA_API_KEY: z.string().min(1),
|
||||
OPENCODE_ZEN_API_KEY: z.string().min(1),
|
||||
GITHUB_TOKEN: z.string().default(""),
|
||||
LOG_LEVEL: z.enum(["debug", "info", "warn", "error"]).default("info"),
|
||||
LOG_PRETTY: z
|
||||
.string()
|
||||
.default("false")
|
||||
.transform((value) => value.toLowerCase() === "true"),
|
||||
HEALTH_HOST: z.string().default("0.0.0.0"),
|
||||
HEALTH_PORT: z.coerce.number().default(8787),
|
||||
TURN_ROUTING_MODE: z.enum(["off", "heuristic", "ai"]).default("ai"),
|
||||
TURN_ROUTING_MODEL: z.string().default("claude-haiku-4-5"),
|
||||
SANDBOX_REUSE_POLICY: z.enum(["resume_preferred", "recreate"]).default("resume_preferred"),
|
||||
SANDBOX_TIMEOUT_MINUTES: z.coerce.number().default(30),
|
||||
PAUSED_TTL_MINUTES: z.coerce.number().default(180),
|
||||
RESUME_HEALTH_TIMEOUT_MS: z.coerce.number().default(120000),
|
||||
SANDBOX_CREATION_TIMEOUT: z.coerce.number().default(180),
|
||||
OPENCODE_MODEL: z.string().default("opencode/claude-sonnet-4-5"),
|
||||
});
|
||||
|
||||
export type Env = z.infer<typeof envSchema>;
|
||||
|
||||
let _config: Env | null = null;
|
||||
|
||||
export function getEnv(): Env {
|
||||
if (!_config) {
|
||||
const result = envSchema.safeParse(process.env);
|
||||
if (!result.success) {
|
||||
console.error(JSON.stringify({
|
||||
ts: new Date().toISOString(),
|
||||
level: "error",
|
||||
event: "config.invalid",
|
||||
component: "config",
|
||||
message: "Invalid environment variables",
|
||||
fieldErrors: result.error.flatten().fieldErrors,
|
||||
}));
|
||||
throw new Error("Invalid environment configuration");
|
||||
}
|
||||
_config = result.data;
|
||||
}
|
||||
return _config;
|
||||
}
|
||||
11
packages/discord/src/db/client.ts
Normal file
11
packages/discord/src/db/client.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { neon } from "@neondatabase/serverless";
|
||||
import { getEnv } from "../config";
|
||||
|
||||
let _sql: ReturnType<typeof neon> | null = null;
|
||||
|
||||
export function getSql() {
|
||||
if (!_sql) {
|
||||
_sql = neon(getEnv().DATABASE_URL);
|
||||
}
|
||||
return _sql;
|
||||
}
|
||||
65
packages/discord/src/db/init.ts
Normal file
65
packages/discord/src/db/init.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { getSql } from "./client";
|
||||
import { logger } from "../observability/logger";
|
||||
|
||||
const PREFIX = "[db]";
|
||||
|
||||
export async function initializeDatabase(): Promise<void> {
|
||||
const sql = getSql();
|
||||
|
||||
await sql`CREATE TABLE IF NOT EXISTS discord_sessions (
|
||||
thread_id TEXT PRIMARY KEY,
|
||||
channel_id TEXT NOT NULL,
|
||||
guild_id TEXT NOT NULL,
|
||||
sandbox_id TEXT NOT NULL,
|
||||
session_id TEXT NOT NULL,
|
||||
preview_url TEXT NOT NULL,
|
||||
preview_token TEXT,
|
||||
status TEXT NOT NULL CHECK (status IN ('creating', 'active', 'pausing', 'paused', 'resuming', 'destroying', 'destroyed', 'error')),
|
||||
last_activity TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
pause_requested_at TIMESTAMPTZ,
|
||||
paused_at TIMESTAMPTZ,
|
||||
resume_attempted_at TIMESTAMPTZ,
|
||||
resumed_at TIMESTAMPTZ,
|
||||
destroyed_at TIMESTAMPTZ,
|
||||
last_health_ok_at TIMESTAMPTZ,
|
||||
last_error TEXT,
|
||||
resume_fail_count INTEGER NOT NULL DEFAULT 0,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
)`;
|
||||
|
||||
await sql`ALTER TABLE discord_sessions ADD COLUMN IF NOT EXISTS preview_token TEXT`;
|
||||
await sql`ALTER TABLE discord_sessions ADD COLUMN IF NOT EXISTS last_activity TIMESTAMPTZ NOT NULL DEFAULT NOW()`;
|
||||
await sql`ALTER TABLE discord_sessions ADD COLUMN IF NOT EXISTS created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()`;
|
||||
await sql`ALTER TABLE discord_sessions ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()`;
|
||||
await sql`ALTER TABLE discord_sessions ADD COLUMN IF NOT EXISTS pause_requested_at TIMESTAMPTZ`;
|
||||
await sql`ALTER TABLE discord_sessions ADD COLUMN IF NOT EXISTS paused_at TIMESTAMPTZ`;
|
||||
await sql`ALTER TABLE discord_sessions ADD COLUMN IF NOT EXISTS resume_attempted_at TIMESTAMPTZ`;
|
||||
await sql`ALTER TABLE discord_sessions ADD COLUMN IF NOT EXISTS resumed_at TIMESTAMPTZ`;
|
||||
await sql`ALTER TABLE discord_sessions ADD COLUMN IF NOT EXISTS destroyed_at TIMESTAMPTZ`;
|
||||
await sql`ALTER TABLE discord_sessions ADD COLUMN IF NOT EXISTS last_health_ok_at TIMESTAMPTZ`;
|
||||
await sql`ALTER TABLE discord_sessions ADD COLUMN IF NOT EXISTS last_error TEXT`;
|
||||
await sql`ALTER TABLE discord_sessions ADD COLUMN IF NOT EXISTS resume_fail_count INTEGER NOT NULL DEFAULT 0`;
|
||||
|
||||
await sql`ALTER TABLE discord_sessions DROP CONSTRAINT IF EXISTS discord_sessions_status_check`;
|
||||
await sql`ALTER TABLE discord_sessions
|
||||
ADD CONSTRAINT discord_sessions_status_check
|
||||
CHECK (status IN ('creating', 'active', 'pausing', 'paused', 'resuming', 'destroying', 'destroyed', 'error'))`;
|
||||
|
||||
await sql`CREATE INDEX IF NOT EXISTS discord_sessions_status_last_activity_idx
|
||||
ON discord_sessions (status, last_activity)`;
|
||||
|
||||
await sql`CREATE INDEX IF NOT EXISTS discord_sessions_status_updated_at_idx
|
||||
ON discord_sessions (status, updated_at)`;
|
||||
}
|
||||
|
||||
if (import.meta.main) {
|
||||
initializeDatabase()
|
||||
.then(() => {
|
||||
logger.info({ event: "db.schema.ready", component: "db", message: `${PREFIX} Schema is ready` });
|
||||
})
|
||||
.catch((err) => {
|
||||
logger.error({ event: "db.schema.failed", component: "db", message: `${PREFIX} Failed to initialize schema`, error: err });
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
14
packages/discord/src/discord/client.ts
Normal file
14
packages/discord/src/discord/client.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { Client, GatewayIntentBits, Partials } from "discord.js";
|
||||
|
||||
export function createDiscordClient(): Client {
|
||||
const client = new Client({
|
||||
intents: [
|
||||
GatewayIntentBits.Guilds,
|
||||
GatewayIntentBits.GuildMessages,
|
||||
GatewayIntentBits.MessageContent,
|
||||
],
|
||||
partials: [Partials.Channel],
|
||||
});
|
||||
|
||||
return client;
|
||||
}
|
||||
70
packages/discord/src/discord/format.ts
Normal file
70
packages/discord/src/discord/format.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
const MAX_MESSAGE_LENGTH = 1900;
|
||||
|
||||
/**
|
||||
* Splits a long response into Discord-safe message chunks (<2000 chars).
|
||||
* Splits at code block boundaries, paragraph breaks, or sentence ends.
|
||||
* Handles unclosed code blocks across chunks.
|
||||
*/
|
||||
export function splitForDiscord(text: string): string[] {
|
||||
if (!text || text.length === 0) return ["(No response)"];
|
||||
if (text.length <= MAX_MESSAGE_LENGTH) return [text];
|
||||
|
||||
const messages: string[] = [];
|
||||
let remaining = text;
|
||||
|
||||
while (remaining.length > 0) {
|
||||
if (remaining.length <= MAX_MESSAGE_LENGTH) {
|
||||
messages.push(remaining);
|
||||
break;
|
||||
}
|
||||
|
||||
// Find best split point
|
||||
let splitAt = -1;
|
||||
|
||||
// Prefer splitting at end of code block
|
||||
splitAt = remaining.lastIndexOf("\n```\n", MAX_MESSAGE_LENGTH);
|
||||
if (splitAt !== -1) splitAt += 4; // include the closing ```\n
|
||||
|
||||
// Then paragraph break
|
||||
if (splitAt === -1 || splitAt < MAX_MESSAGE_LENGTH / 2) {
|
||||
const paraBreak = remaining.lastIndexOf("\n\n", MAX_MESSAGE_LENGTH);
|
||||
if (paraBreak > MAX_MESSAGE_LENGTH / 2) splitAt = paraBreak;
|
||||
}
|
||||
|
||||
// Then sentence end
|
||||
if (splitAt === -1 || splitAt < MAX_MESSAGE_LENGTH / 2) {
|
||||
const sentenceEnd = remaining.lastIndexOf(". ", MAX_MESSAGE_LENGTH);
|
||||
if (sentenceEnd > MAX_MESSAGE_LENGTH / 2) splitAt = sentenceEnd + 1;
|
||||
}
|
||||
|
||||
// Fallback: hard cut
|
||||
if (splitAt === -1 || splitAt < MAX_MESSAGE_LENGTH / 4) {
|
||||
splitAt = MAX_MESSAGE_LENGTH;
|
||||
}
|
||||
|
||||
const chunk = remaining.slice(0, splitAt);
|
||||
remaining = remaining.slice(splitAt).trimStart();
|
||||
|
||||
// Handle unclosed code blocks
|
||||
const backtickCount = (chunk.match(/```/g) || []).length;
|
||||
if (backtickCount % 2 !== 0) {
|
||||
// Odd = unclosed code block
|
||||
messages.push(chunk + "\n```");
|
||||
remaining = "```\n" + remaining;
|
||||
} else {
|
||||
messages.push(chunk);
|
||||
}
|
||||
}
|
||||
|
||||
return messages.filter((m) => m.trim().length > 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up the response text for Discord.
|
||||
* Strips any leading/trailing whitespace and limits consecutive newlines.
|
||||
*/
|
||||
export function cleanResponse(text: string): string {
|
||||
return text
|
||||
.trim()
|
||||
.replace(/\n{4,}/g, "\n\n\n"); // max 3 consecutive newlines
|
||||
}
|
||||
321
packages/discord/src/discord/handlers/message-create.ts
Normal file
321
packages/discord/src/discord/handlers/message-create.ts
Normal file
@@ -0,0 +1,321 @@
|
||||
import type { Client, Message, TextChannel, ThreadChannel, GuildMember } from "discord.js";
|
||||
import { ChannelType } from "discord.js";
|
||||
import { getEnv } from "../../config";
|
||||
import { cleanResponse, splitForDiscord } from "../format";
|
||||
import { shouldRespondToOwnedThreadTurn } from "../turn-routing";
|
||||
import { generateThreadName } from "../thread-name";
|
||||
import type { SandboxManager } from "../../sandbox/manager";
|
||||
import { logger } from "../../observability/logger";
|
||||
|
||||
/**
|
||||
* Checks if a channel (or its parent for threads) is allowed.
|
||||
* Allowed means: in the ALLOWED_CHANNEL_IDS list, OR in the DISCORD_CATEGORY_ID category.
|
||||
*/
|
||||
function isChannelAllowed(channelId: string, categoryId: string | null, env: ReturnType<typeof getEnv>): boolean {
|
||||
if (env.ALLOWED_CHANNEL_IDS.length > 0 && env.ALLOWED_CHANNEL_IDS.includes(channelId)) {
|
||||
return true;
|
||||
}
|
||||
if (env.DISCORD_CATEGORY_ID && categoryId === env.DISCORD_CATEGORY_ID) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a user has the required role (if configured).
|
||||
*/
|
||||
function hasRequiredRole(member: GuildMember | null, env: ReturnType<typeof getEnv>): boolean {
|
||||
if (!env.DISCORD_REQUIRED_ROLE_ID) return true; // no role requirement
|
||||
if (!member) return false;
|
||||
return member.roles.cache.has(env.DISCORD_REQUIRED_ROLE_ID);
|
||||
}
|
||||
|
||||
const HISTORY_FETCH_LIMIT = 40;
|
||||
const HISTORY_LINE_CHAR_LIMIT = 500;
|
||||
const HISTORY_TOTAL_CHAR_LIMIT = 6000;
|
||||
|
||||
function normalizeHistoryText(value: string): string {
|
||||
return value.replace(/\s+/g, " ").trim();
|
||||
}
|
||||
|
||||
async function buildHistoryReplayPrompt(
|
||||
thread: ThreadChannel,
|
||||
currentMessage: Message,
|
||||
latestUserContent: string,
|
||||
): Promise<{ prompt: string; historyCount: number }> {
|
||||
try {
|
||||
const fetched = await thread.messages.fetch({ limit: HISTORY_FETCH_LIMIT });
|
||||
const ordered = [...fetched.values()].sort((a, b) => a.createdTimestamp - b.createdTimestamp);
|
||||
|
||||
const lines: string[] = [];
|
||||
for (const prior of ordered) {
|
||||
if (prior.id === currentMessage.id || prior.system) continue;
|
||||
|
||||
let lineContent = normalizeHistoryText(prior.content);
|
||||
if (!lineContent && prior.attachments.size > 0) {
|
||||
const files = [...prior.attachments.values()].map((att) => att.name ?? "file").join(", ");
|
||||
lineContent = `[attachments: ${files}]`;
|
||||
}
|
||||
|
||||
if (!lineContent) continue;
|
||||
if (lineContent.length > HISTORY_LINE_CHAR_LIMIT) {
|
||||
lineContent = `${lineContent.slice(0, HISTORY_LINE_CHAR_LIMIT)}...`;
|
||||
}
|
||||
|
||||
const role = prior.author.bot ? "assistant" : "user";
|
||||
lines.push(`${role}: ${lineContent}`);
|
||||
}
|
||||
|
||||
if (lines.length === 0) {
|
||||
return { prompt: latestUserContent, historyCount: 0 };
|
||||
}
|
||||
|
||||
const selected: string[] = [];
|
||||
let totalChars = 0;
|
||||
for (let i = lines.length - 1; i >= 0; i -= 1) {
|
||||
const candidate = lines[i];
|
||||
if (totalChars + candidate.length > HISTORY_TOTAL_CHAR_LIMIT && selected.length > 0) break;
|
||||
selected.unshift(candidate);
|
||||
totalChars += candidate.length;
|
||||
}
|
||||
|
||||
return {
|
||||
prompt: [
|
||||
"Conversation history from this same Discord thread (oldest to newest):",
|
||||
selected.join("\n"),
|
||||
"",
|
||||
"Continue the same conversation and respond to the latest user message:",
|
||||
latestUserContent,
|
||||
].join("\n"),
|
||||
historyCount: selected.length,
|
||||
};
|
||||
} catch {
|
||||
return { prompt: latestUserContent, historyCount: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a messageCreate event handler bound to the given SandboxManager.
|
||||
*/
|
||||
export function createMessageHandler(client: Client, sandboxManager: SandboxManager) {
|
||||
const env = getEnv();
|
||||
|
||||
return async (message: Message): Promise<void> => {
|
||||
if (message.author.bot) return;
|
||||
|
||||
// Check if the bot is mentioned (ignore @everyone and @here)
|
||||
if (message.mentions.everyone) return;
|
||||
|
||||
const botUserId = client.user?.id ?? "";
|
||||
const roleId = env.DISCORD_ROLE_ID;
|
||||
|
||||
const mentionedByUser = client.user ? message.mentions.has(client.user, { ignoreEveryone: true, ignoreRoles: false }) : false;
|
||||
const mentionedByRole = roleId ? message.mentions.roles.has(roleId) : false;
|
||||
const mentionedInContent = message.content.includes(`<@${botUserId}>`) || message.content.includes(`<@!${botUserId}>`);
|
||||
const roleMentionInContent = roleId ? message.content.includes(`<@&${roleId}>`) : false;
|
||||
|
||||
const isMentioned = mentionedByUser || mentionedByRole || mentionedInContent || roleMentionInContent;
|
||||
|
||||
// In threads the bot owns, respond to ALL messages (no mention needed)
|
||||
const isInThread = message.channel.type === ChannelType.PublicThread || message.channel.type === ChannelType.PrivateThread;
|
||||
let isOwnedThread = false;
|
||||
if (isInThread && !isMentioned) {
|
||||
isOwnedThread = await sandboxManager.hasTrackedThread(message.channelId);
|
||||
|
||||
if (isOwnedThread) {
|
||||
const decision = await shouldRespondToOwnedThreadTurn({
|
||||
mode: env.TURN_ROUTING_MODE,
|
||||
model: env.TURN_ROUTING_MODEL,
|
||||
apiKey: env.OPENCODE_ZEN_API_KEY,
|
||||
content: message.content,
|
||||
botUserId,
|
||||
botRoleId: roleId,
|
||||
mentionedUserIds: [...message.mentions.users.keys()],
|
||||
mentionedRoleIds: [...message.mentions.roles.keys()],
|
||||
});
|
||||
|
||||
if (!decision.shouldRespond) {
|
||||
logger.info({
|
||||
event: "discord.message.skipped.not_directed",
|
||||
component: "message-handler",
|
||||
message: "Skipped turn not directed at bot",
|
||||
channelId: message.channelId,
|
||||
userId: message.author.id,
|
||||
reason: decision.reason,
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isMentioned && !isOwnedThread) return;
|
||||
|
||||
// Check required role
|
||||
const member = message.member;
|
||||
if (!hasRequiredRole(member, env)) {
|
||||
logger.info({
|
||||
event: "discord.message.ignored.role",
|
||||
component: "message-handler",
|
||||
message: "Ignored message from user without required role",
|
||||
channelId: message.channelId,
|
||||
userId: message.author.id,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info({
|
||||
event: "discord.message.triggered",
|
||||
component: "message-handler",
|
||||
message: "Bot triggered",
|
||||
channelId: message.channelId,
|
||||
userId: message.author.id,
|
||||
isMentioned,
|
||||
isOwnedThread,
|
||||
contentLength: message.content.length,
|
||||
});
|
||||
|
||||
// Strip mentions from content
|
||||
const content = message.content.replace(/<@[!&]?\d+>/g, "").trim();
|
||||
if (!content) {
|
||||
await message.reply("Tag me with a question!").catch(() => {});
|
||||
return;
|
||||
}
|
||||
|
||||
let thread: ThreadChannel;
|
||||
let parentChannelId: string;
|
||||
let parentCategoryId: string | null = null;
|
||||
|
||||
try {
|
||||
if (isInThread) {
|
||||
thread = message.channel as ThreadChannel;
|
||||
parentChannelId = thread.parentId ?? "";
|
||||
// Get the category from the parent channel
|
||||
const parentChannel = thread.parent;
|
||||
parentCategoryId = parentChannel?.parentId ?? null;
|
||||
} else {
|
||||
parentChannelId = message.channelId;
|
||||
parentCategoryId = (message.channel as TextChannel).parentId ?? null;
|
||||
|
||||
if (!isChannelAllowed(parentChannelId, parentCategoryId, env)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const threadName = await generateThreadName(content);
|
||||
thread = await (message.channel as TextChannel).threads.create({
|
||||
name: threadName,
|
||||
startMessage: message,
|
||||
autoArchiveDuration: 60,
|
||||
});
|
||||
}
|
||||
|
||||
// Check allowed for threads too
|
||||
if (!isOwnedThread && !isChannelAllowed(parentChannelId, parentCategoryId, env)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const threadId = thread.id;
|
||||
const channelId = parentChannelId;
|
||||
const guildId = message.guildId ?? "";
|
||||
|
||||
const trackedBeforeResolve = await sandboxManager.getTrackedSession(threadId);
|
||||
|
||||
// Typing indicator
|
||||
let typingActive = true;
|
||||
const sendTyping = () => {
|
||||
if (typingActive) thread.sendTyping().catch(() => {});
|
||||
};
|
||||
sendTyping();
|
||||
const typingInterval = setInterval(sendTyping, 8000);
|
||||
|
||||
try {
|
||||
let session = await sandboxManager.resolveSessionForMessage(threadId, channelId, guildId);
|
||||
let historyPromptCache: { prompt: string; historyCount: number } | null = null;
|
||||
|
||||
const getHistoryPrompt = async (): Promise<{ prompt: string; historyCount: number }> => {
|
||||
if (!historyPromptCache) {
|
||||
historyPromptCache = await buildHistoryReplayPrompt(thread, message, content);
|
||||
}
|
||||
return historyPromptCache;
|
||||
};
|
||||
|
||||
let promptForAgent = content;
|
||||
if (trackedBeforeResolve && trackedBeforeResolve.sessionId !== session.sessionId) {
|
||||
const replay = await getHistoryPrompt();
|
||||
promptForAgent = replay.prompt;
|
||||
logger.info({
|
||||
event: "discord.context.replayed",
|
||||
component: "message-handler",
|
||||
message: "Replayed thread history into replacement session",
|
||||
threadId,
|
||||
previousSessionId: trackedBeforeResolve.sessionId,
|
||||
sessionId: session.sessionId,
|
||||
historyMessages: replay.historyCount,
|
||||
});
|
||||
}
|
||||
|
||||
let response: string;
|
||||
try {
|
||||
response = await sandboxManager.sendMessage(session, promptForAgent);
|
||||
} catch (err: any) {
|
||||
if (err?.recoverable && err?.message === "SANDBOX_DEAD") {
|
||||
logger.warn({
|
||||
event: "discord.message.recovering",
|
||||
component: "message-handler",
|
||||
message: "Recovering by resolving session again",
|
||||
threadId,
|
||||
});
|
||||
await thread.send("*Session changed state, recovering...*").catch(() => {});
|
||||
const sessionBeforeRecovery = session.sessionId;
|
||||
session = await sandboxManager.resolveSessionForMessage(threadId, channelId, guildId);
|
||||
let recoveryPrompt = content;
|
||||
if (sessionBeforeRecovery !== session.sessionId) {
|
||||
const replay = await getHistoryPrompt();
|
||||
recoveryPrompt = replay.prompt;
|
||||
logger.info({
|
||||
event: "discord.context.replayed",
|
||||
component: "message-handler",
|
||||
message: "Replayed thread history after recovery",
|
||||
threadId,
|
||||
previousSessionId: sessionBeforeRecovery,
|
||||
sessionId: session.sessionId,
|
||||
historyMessages: replay.historyCount,
|
||||
});
|
||||
}
|
||||
response = await sandboxManager.sendMessage(session, recoveryPrompt);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
const cleaned = cleanResponse(response);
|
||||
const chunks = splitForDiscord(cleaned);
|
||||
|
||||
for (const chunk of chunks) {
|
||||
await thread.send(chunk);
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error({
|
||||
event: "discord.message.failed",
|
||||
component: "message-handler",
|
||||
message: "Error handling message",
|
||||
threadId,
|
||||
error: err,
|
||||
});
|
||||
const errorMsg = err instanceof Error ? err.message : "Unknown error";
|
||||
await thread.send(`Something went wrong: ${errorMsg}`).catch(() => {});
|
||||
} finally {
|
||||
typingActive = false;
|
||||
clearInterval(typingInterval);
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error({
|
||||
event: "discord.message.setup_failed",
|
||||
component: "message-handler",
|
||||
message: "Error processing message",
|
||||
channelId: message.channelId,
|
||||
error: err,
|
||||
});
|
||||
await message.reply("Something went wrong setting up the thread.").catch(() => {});
|
||||
}
|
||||
};
|
||||
}
|
||||
59
packages/discord/src/discord/thread-name.ts
Normal file
59
packages/discord/src/discord/thread-name.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { getEnv } from "../config";
|
||||
|
||||
const ZEN_MESSAGES_URL = "https://opencode.ai/zen/v1/messages";
|
||||
|
||||
/**
|
||||
* Uses Claude Haiku 4.5 via OpenCode Zen to generate a concise thread name
|
||||
* from the user's message. Falls back to truncation on error.
|
||||
*/
|
||||
export async function generateThreadName(userMessage: string): Promise<string> {
|
||||
try {
|
||||
const env = getEnv();
|
||||
|
||||
const res = await fetch(ZEN_MESSAGES_URL, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"x-api-key": env.OPENCODE_ZEN_API_KEY,
|
||||
"anthropic-version": "2023-06-01",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: "claude-haiku-4-5",
|
||||
max_tokens: 60,
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: `Generate a short, descriptive thread title (max 90 chars) for this Discord question. Return ONLY the title, no quotes, no explanation.\n\nQuestion: ${userMessage}`,
|
||||
},
|
||||
],
|
||||
}),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
console.warn(`[thread-name] Zen API returned ${res.status}, falling back to truncation`);
|
||||
return fallback(userMessage);
|
||||
}
|
||||
|
||||
const data = await res.json() as {
|
||||
content?: Array<{ type: string; text?: string }>;
|
||||
};
|
||||
|
||||
const title = data.content
|
||||
?.filter((c) => c.type === "text")
|
||||
.map((c) => c.text ?? "")
|
||||
.join("")
|
||||
.trim();
|
||||
|
||||
if (!title || title.length === 0) return fallback(userMessage);
|
||||
|
||||
// Ensure it fits Discord's thread name limit (100 chars)
|
||||
return title.slice(0, 95) + (title.length > 95 ? "..." : "");
|
||||
} catch (err) {
|
||||
console.warn("[thread-name] Failed to generate name:", err);
|
||||
return fallback(userMessage);
|
||||
}
|
||||
}
|
||||
|
||||
function fallback(message: string): string {
|
||||
return message.slice(0, 95) + (message.length > 95 ? "..." : "");
|
||||
}
|
||||
127
packages/discord/src/discord/turn-routing.ts
Normal file
127
packages/discord/src/discord/turn-routing.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
const ZEN_MESSAGES_URL = "https://opencode.ai/zen/v1/messages";
|
||||
|
||||
export type TurnRoutingMode = "off" | "heuristic" | "ai";
|
||||
|
||||
type TurnRoutingInput = {
|
||||
mode: TurnRoutingMode;
|
||||
model: string;
|
||||
apiKey: string;
|
||||
content: string;
|
||||
botUserId: string;
|
||||
botRoleId: string;
|
||||
mentionedUserIds: string[];
|
||||
mentionedRoleIds: string[];
|
||||
};
|
||||
|
||||
export type TurnRoutingDecision = {
|
||||
shouldRespond: boolean;
|
||||
reason: string;
|
||||
};
|
||||
|
||||
const QUICK_CHAT_RE = /^(ok|okay|k|kk|thanks|thank you|thx|lol|lmao|haha|nice|cool|yup|yep|nah|nope|got it|sgtm)[!. ]*$/i;
|
||||
|
||||
function heuristicDecision(input: TurnRoutingInput): TurnRoutingDecision | null {
|
||||
const text = input.content.trim();
|
||||
const lower = text.toLowerCase();
|
||||
|
||||
if (!text) {
|
||||
return { shouldRespond: false, reason: "empty-message" };
|
||||
}
|
||||
|
||||
const mentionsOtherUser = input.mentionedUserIds.some((id) => id !== input.botUserId);
|
||||
if (mentionsOtherUser) {
|
||||
return { shouldRespond: false, reason: "mentions-other-user" };
|
||||
}
|
||||
|
||||
const mentionsOtherRole = input.mentionedRoleIds.some((id) => id !== input.botRoleId);
|
||||
if (mentionsOtherRole) {
|
||||
return { shouldRespond: false, reason: "mentions-other-role" };
|
||||
}
|
||||
|
||||
if (text.length <= 40 && QUICK_CHAT_RE.test(text)) {
|
||||
return { shouldRespond: false, reason: "quick-chat" };
|
||||
}
|
||||
|
||||
if (/\b(opencode|bot)\b/i.test(text)) {
|
||||
return { shouldRespond: true, reason: "bot-keyword" };
|
||||
}
|
||||
|
||||
if (text.includes("?") && /\b(you|your|can you|could you|would you|please|help)\b/i.test(text)) {
|
||||
return { shouldRespond: true, reason: "direct-question" };
|
||||
}
|
||||
|
||||
if (text.includes("?") && /\b(how|what|why|where|when|which)\b/i.test(text)) {
|
||||
return { shouldRespond: true, reason: "general-question" };
|
||||
}
|
||||
|
||||
if (lower.startsWith("do this") || lower.startsWith("run ") || lower.startsWith("fix ")) {
|
||||
return { shouldRespond: true, reason: "instruction" };
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
async function aiDecision(input: TurnRoutingInput): Promise<TurnRoutingDecision> {
|
||||
const prompt = [
|
||||
"You route turns for an engineering Discord bot.",
|
||||
"Decide if the latest message is directed at the bot assistant or is side conversation.",
|
||||
"Return EXACTLY one token: RESPOND or SKIP.",
|
||||
"",
|
||||
`Message: ${input.content}`,
|
||||
`MentionsOtherUser: ${input.mentionedUserIds.some((id) => id !== input.botUserId)}`,
|
||||
`MentionsOtherRole: ${input.mentionedRoleIds.some((id) => id !== input.botRoleId)}`,
|
||||
].join("\n");
|
||||
|
||||
const res = await fetch(ZEN_MESSAGES_URL, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"x-api-key": input.apiKey,
|
||||
"anthropic-version": "2023-06-01",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: input.model,
|
||||
max_tokens: 10,
|
||||
messages: [{ role: "user", content: prompt }],
|
||||
}),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
return { shouldRespond: true, reason: `ai-http-${res.status}` };
|
||||
}
|
||||
|
||||
const data = await res.json() as { content?: Array<{ type: string; text?: string }> };
|
||||
const output = data.content
|
||||
?.filter((c) => c.type === "text")
|
||||
.map((c) => c.text ?? "")
|
||||
.join(" ")
|
||||
.trim()
|
||||
.toUpperCase();
|
||||
|
||||
if (output?.includes("SKIP")) {
|
||||
return { shouldRespond: false, reason: "ai-skip" };
|
||||
}
|
||||
|
||||
return { shouldRespond: true, reason: output?.includes("RESPOND") ? "ai-respond" : "ai-default-respond" };
|
||||
}
|
||||
|
||||
export async function shouldRespondToOwnedThreadTurn(input: TurnRoutingInput): Promise<TurnRoutingDecision> {
|
||||
if (input.mode === "off") {
|
||||
return { shouldRespond: true, reason: "routing-off" };
|
||||
}
|
||||
|
||||
const heuristic = heuristicDecision(input);
|
||||
if (heuristic) {
|
||||
return heuristic;
|
||||
}
|
||||
|
||||
if (input.mode === "heuristic") {
|
||||
return { shouldRespond: true, reason: "heuristic-uncertain-default-respond" };
|
||||
}
|
||||
|
||||
try {
|
||||
return await aiDecision(input);
|
||||
} catch {
|
||||
return { shouldRespond: true, reason: "ai-error-default-respond" };
|
||||
}
|
||||
}
|
||||
43
packages/discord/src/http/health.ts
Normal file
43
packages/discord/src/http/health.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import type { Client } from "discord.js";
|
||||
|
||||
type HealthDependencies = {
|
||||
client: Client;
|
||||
isCleanupLoopRunning: () => boolean;
|
||||
getActiveSessionCount: () => Promise<number>;
|
||||
};
|
||||
|
||||
export function startHealthServer(host: string, port: number, deps: HealthDependencies): Bun.Server<unknown> {
|
||||
const startedAt = Date.now();
|
||||
|
||||
return Bun.serve({
|
||||
hostname: host,
|
||||
port,
|
||||
fetch: async (request) => {
|
||||
const url = new URL(request.url);
|
||||
|
||||
if (url.pathname === "/healthz") {
|
||||
const activeSessions = await deps.getActiveSessionCount().catch(() => 0);
|
||||
return Response.json({
|
||||
ok: true,
|
||||
uptimeSec: Math.floor((Date.now() - startedAt) / 1000),
|
||||
discordReady: deps.client.isReady(),
|
||||
cleanupLoopRunning: deps.isCleanupLoopRunning(),
|
||||
activeSessions,
|
||||
});
|
||||
}
|
||||
|
||||
if (url.pathname === "/readyz") {
|
||||
const ready = deps.client.isReady();
|
||||
return Response.json(
|
||||
{
|
||||
ok: ready,
|
||||
discordReady: ready,
|
||||
},
|
||||
{ status: ready ? 200 : 503 },
|
||||
);
|
||||
}
|
||||
|
||||
return new Response("Not Found", { status: 404 });
|
||||
},
|
||||
});
|
||||
}
|
||||
83
packages/discord/src/index.ts
Normal file
83
packages/discord/src/index.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { getEnv } from "./config";
|
||||
import { createDiscordClient } from "./discord/client";
|
||||
import { createMessageHandler } from "./discord/handlers/message-create";
|
||||
import { initializeDatabase } from "./db/init";
|
||||
import { startHealthServer } from "./http/health";
|
||||
import { logger } from "./observability/logger";
|
||||
import { SandboxManager } from "./sandbox/manager";
|
||||
|
||||
async function main() {
|
||||
const env = getEnv();
|
||||
logger.info({ event: "app.starting", component: "index", message: "Starting Discord bot" });
|
||||
await initializeDatabase();
|
||||
logger.info({ event: "db.ready", component: "index", message: "Database ready" });
|
||||
|
||||
const client = createDiscordClient();
|
||||
const sandboxManager = new SandboxManager();
|
||||
const healthServer = startHealthServer(env.HEALTH_HOST, env.HEALTH_PORT, {
|
||||
client,
|
||||
isCleanupLoopRunning: () => sandboxManager.isCleanupLoopRunning(),
|
||||
getActiveSessionCount: () => sandboxManager.getActiveSessionCount(),
|
||||
});
|
||||
|
||||
logger.info({
|
||||
event: "health.server.started",
|
||||
component: "index",
|
||||
message: "Health server started",
|
||||
host: env.HEALTH_HOST,
|
||||
port: env.HEALTH_PORT,
|
||||
});
|
||||
|
||||
// Register message handler
|
||||
const messageHandler = createMessageHandler(client, sandboxManager);
|
||||
client.on("messageCreate", messageHandler);
|
||||
|
||||
// Ready event
|
||||
client.on("clientReady", () => {
|
||||
logger.info({
|
||||
event: "discord.ready",
|
||||
component: "index",
|
||||
message: "Discord client ready",
|
||||
tag: client.user?.tag,
|
||||
allowedChannels: env.ALLOWED_CHANNEL_IDS,
|
||||
});
|
||||
sandboxManager.startCleanupLoop();
|
||||
});
|
||||
|
||||
// Login
|
||||
await client.login(env.DISCORD_TOKEN);
|
||||
|
||||
let shuttingDown = false;
|
||||
|
||||
// Graceful shutdown
|
||||
const shutdown = async (signal: string) => {
|
||||
if (shuttingDown) return;
|
||||
shuttingDown = true;
|
||||
|
||||
logger.info({
|
||||
event: "app.shutdown.start",
|
||||
component: "index",
|
||||
message: "Shutting down",
|
||||
signal,
|
||||
});
|
||||
healthServer.stop();
|
||||
sandboxManager.stopCleanupLoop();
|
||||
await sandboxManager.destroyAll();
|
||||
client.destroy();
|
||||
logger.info({ event: "app.shutdown.complete", component: "index", message: "Shutdown complete" });
|
||||
process.exit(0);
|
||||
};
|
||||
|
||||
process.on("SIGINT", () => shutdown("SIGINT"));
|
||||
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
logger.error({
|
||||
event: "app.fatal",
|
||||
component: "index",
|
||||
message: "Fatal error",
|
||||
error: err,
|
||||
});
|
||||
process.exit(1);
|
||||
});
|
||||
76
packages/discord/src/observability/logger.ts
Normal file
76
packages/discord/src/observability/logger.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import { getEnv } from "../config";
|
||||
|
||||
type LogLevel = "debug" | "info" | "warn" | "error";
|
||||
|
||||
type LogFields = {
|
||||
event: string;
|
||||
message: string;
|
||||
component?: string;
|
||||
threadId?: string;
|
||||
channelId?: string;
|
||||
guildId?: string;
|
||||
sandboxId?: string;
|
||||
sessionId?: string;
|
||||
durationMs?: number;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
const levelOrder: Record<LogLevel, number> = {
|
||||
debug: 10,
|
||||
info: 20,
|
||||
warn: 30,
|
||||
error: 40,
|
||||
};
|
||||
|
||||
function shouldLog(level: LogLevel): boolean {
|
||||
const env = getEnv();
|
||||
return levelOrder[level] >= levelOrder[env.LOG_LEVEL];
|
||||
}
|
||||
|
||||
function serializeError(err: unknown) {
|
||||
if (!(err instanceof Error)) return err;
|
||||
return {
|
||||
name: err.name,
|
||||
message: err.message,
|
||||
stack: err.stack,
|
||||
};
|
||||
}
|
||||
|
||||
function write(level: LogLevel, fields: LogFields): void {
|
||||
if (!shouldLog(level)) return;
|
||||
|
||||
const env = getEnv();
|
||||
const payload = {
|
||||
ts: new Date().toISOString(),
|
||||
level,
|
||||
...fields,
|
||||
};
|
||||
|
||||
if (env.LOG_PRETTY) {
|
||||
const line = `[${payload.ts}] ${level.toUpperCase()} ${fields.event} ${fields.message}`;
|
||||
console.log(line, JSON.stringify(payload));
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(JSON.stringify(payload));
|
||||
}
|
||||
|
||||
export const logger = {
|
||||
debug(fields: LogFields) {
|
||||
write("debug", fields);
|
||||
},
|
||||
info(fields: LogFields) {
|
||||
write("info", fields);
|
||||
},
|
||||
warn(fields: LogFields) {
|
||||
write("warn", fields);
|
||||
},
|
||||
error(fields: LogFields & { error?: unknown }) {
|
||||
write("error", {
|
||||
...fields,
|
||||
error: fields.error ? serializeError(fields.error) : undefined,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export type { LogFields, LogLevel };
|
||||
15
packages/discord/src/sandbox/image.ts
Normal file
15
packages/discord/src/sandbox/image.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Image } from "@daytonaio/sdk";
|
||||
|
||||
/**
|
||||
* Custom Daytona sandbox image with git, gh CLI, opencode, and bun.
|
||||
* Cached by Daytona for 24h — subsequent creates are near-instant.
|
||||
*/
|
||||
export function getDiscordBotImage() {
|
||||
return Image.base("node:22-bookworm-slim")
|
||||
.runCommands(
|
||||
"apt-get update && apt-get install -y git curl && rm -rf /var/lib/apt/lists/*",
|
||||
"curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg -o /usr/share/keyrings/githubcli-archive-keyring.gpg && echo \"deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main\" > /etc/apt/sources.list.d/github-cli.list && apt-get update && apt-get install -y gh && rm -rf /var/lib/apt/lists/*",
|
||||
"npm install -g opencode-ai@latest bun",
|
||||
)
|
||||
.workdir("/home/daytona");
|
||||
}
|
||||
735
packages/discord/src/sandbox/manager.ts
Normal file
735
packages/discord/src/sandbox/manager.ts
Normal file
@@ -0,0 +1,735 @@
|
||||
import { Daytona } from "@daytonaio/sdk";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { getEnv } from "../config";
|
||||
import { logger } from "../observability/logger";
|
||||
import { getSessionStore } from "../sessions/store";
|
||||
import type { SessionInfo, SessionStatus } from "../types";
|
||||
import { getDiscordBotImage } from "./image";
|
||||
import { createSession, listSessions, sendPrompt, sessionExists, waitForHealthy } from "./opencode-client";
|
||||
|
||||
/** In-memory timeout handles keyed by threadId */
|
||||
const timeouts = new Map<string, ReturnType<typeof setTimeout>>();
|
||||
|
||||
type ResumeAttemptResult = {
|
||||
session: SessionInfo | null;
|
||||
allowRecreate: boolean;
|
||||
};
|
||||
|
||||
function timer() {
|
||||
const start = Date.now();
|
||||
return {
|
||||
elapsedMs: () => Date.now() - start,
|
||||
};
|
||||
}
|
||||
|
||||
function createDaytona() {
|
||||
return new Daytona({
|
||||
apiKey: getEnv().DAYTONA_API_KEY,
|
||||
_experimental: {},
|
||||
});
|
||||
}
|
||||
|
||||
function isSandboxMissingError(error: unknown): boolean {
|
||||
const message = (error instanceof Error ? error.message : String(error)).toLowerCase();
|
||||
return message.includes("not found") || message.includes("does not exist") || message.includes("destroyed");
|
||||
}
|
||||
|
||||
async function exec(
|
||||
sandbox: {
|
||||
process: {
|
||||
executeCommand: (
|
||||
cmd: string,
|
||||
cwd?: string,
|
||||
env?: Record<string, string>,
|
||||
timeout?: number,
|
||||
) => Promise<{ exitCode: number; result: string }>;
|
||||
};
|
||||
},
|
||||
label: string,
|
||||
command: string,
|
||||
context: Pick<SessionInfo, "threadId" | "sandboxId">,
|
||||
options?: { cwd?: string; env?: Record<string, string> },
|
||||
): Promise<string> {
|
||||
const t = timer();
|
||||
const result = await sandbox.process.executeCommand(command, options?.cwd, options?.env);
|
||||
|
||||
if (result.exitCode !== 0) {
|
||||
logger.error({
|
||||
event: "sandbox.exec.failed",
|
||||
component: "sandbox-manager",
|
||||
message: "Sandbox command failed",
|
||||
threadId: context.threadId,
|
||||
sandboxId: context.sandboxId,
|
||||
label,
|
||||
exitCode: result.exitCode,
|
||||
durationMs: t.elapsedMs(),
|
||||
stdout: result.result.slice(0, 500),
|
||||
});
|
||||
throw new Error(`${label} failed (exit ${result.exitCode})`);
|
||||
}
|
||||
|
||||
logger.debug({
|
||||
event: "sandbox.exec.ok",
|
||||
component: "sandbox-manager",
|
||||
message: "Sandbox command completed",
|
||||
threadId: context.threadId,
|
||||
sandboxId: context.sandboxId,
|
||||
label,
|
||||
durationMs: t.elapsedMs(),
|
||||
});
|
||||
|
||||
return result.result.trim();
|
||||
}
|
||||
|
||||
export class SandboxManager {
|
||||
private cleanupInterval: ReturnType<typeof setInterval> | null = null;
|
||||
private readonly store = getSessionStore();
|
||||
private readonly threadLocks = new Map<string, Promise<void>>();
|
||||
|
||||
async getActiveSessionCount(): Promise<number> {
|
||||
return (await this.store.listActive()).length;
|
||||
}
|
||||
|
||||
isCleanupLoopRunning(): boolean {
|
||||
return this.cleanupInterval !== null;
|
||||
}
|
||||
|
||||
async hasTrackedThread(threadId: string): Promise<boolean> {
|
||||
return this.store.hasTrackedThread(threadId);
|
||||
}
|
||||
|
||||
async getTrackedSession(threadId: string): Promise<SessionInfo | null> {
|
||||
return this.store.getByThread(threadId);
|
||||
}
|
||||
|
||||
async getSession(threadId: string): Promise<SessionInfo | null> {
|
||||
return this.store.getActive(threadId);
|
||||
}
|
||||
|
||||
async resolveSessionForMessage(threadId: string, channelId: string, guildId: string): Promise<SessionInfo> {
|
||||
return this.withThreadLock(threadId, async () => {
|
||||
const existing = await this.store.getByThread(threadId);
|
||||
const env = getEnv();
|
||||
|
||||
if (!existing) {
|
||||
return this.createSessionUnlocked(threadId, channelId, guildId);
|
||||
}
|
||||
|
||||
let candidate = existing;
|
||||
|
||||
if (candidate.status === "active") {
|
||||
const healthy = await this.ensureSessionHealthy(candidate, 15_000);
|
||||
if (healthy) return candidate;
|
||||
candidate = (await this.store.getByThread(threadId)) ?? { ...candidate, status: "error" };
|
||||
}
|
||||
|
||||
if (env.SANDBOX_REUSE_POLICY === "resume_preferred") {
|
||||
const resumed = await this.tryResumeSession(candidate);
|
||||
if (resumed.session) return resumed.session;
|
||||
|
||||
if (!resumed.allowRecreate) {
|
||||
throw new Error("Unable to reattach to existing sandbox session. Try again shortly.");
|
||||
}
|
||||
}
|
||||
|
||||
return this.createSessionUnlocked(threadId, channelId, guildId);
|
||||
});
|
||||
}
|
||||
|
||||
async createSession(threadId: string, channelId: string, guildId: string): Promise<SessionInfo> {
|
||||
return this.withThreadLock(threadId, async () => this.createSessionUnlocked(threadId, channelId, guildId));
|
||||
}
|
||||
|
||||
private async createSessionUnlocked(threadId: string, channelId: string, guildId: string): Promise<SessionInfo> {
|
||||
const env = getEnv();
|
||||
const totalTimer = timer();
|
||||
|
||||
await this.store.updateStatus(threadId, "creating").catch(() => {});
|
||||
|
||||
const daytona = createDaytona();
|
||||
const image = getDiscordBotImage();
|
||||
const sandbox = await daytona.create(
|
||||
{
|
||||
image,
|
||||
labels: {
|
||||
app: "opencord",
|
||||
threadId,
|
||||
guildId,
|
||||
},
|
||||
autoStopInterval: 0,
|
||||
autoArchiveInterval: 0,
|
||||
},
|
||||
{ timeout: env.SANDBOX_CREATION_TIMEOUT },
|
||||
);
|
||||
|
||||
const sandboxId = sandbox.id;
|
||||
logger.info({
|
||||
event: "sandbox.create.started",
|
||||
component: "sandbox-manager",
|
||||
message: "Created sandbox",
|
||||
threadId,
|
||||
channelId,
|
||||
guildId,
|
||||
sandboxId,
|
||||
});
|
||||
|
||||
try {
|
||||
const context = { threadId, sandboxId };
|
||||
const home = await exec(sandbox, "discover-home", "echo $HOME", context);
|
||||
|
||||
await exec(
|
||||
sandbox,
|
||||
"clone-opencode",
|
||||
`git clone --depth=1 https://github.com/anomalyco/opencode.git ${home}/opencode`,
|
||||
context,
|
||||
);
|
||||
|
||||
const authJson = JSON.stringify({
|
||||
opencode: { type: "api", key: env.OPENCODE_ZEN_API_KEY },
|
||||
});
|
||||
|
||||
await exec(
|
||||
sandbox,
|
||||
"write-auth",
|
||||
`mkdir -p ${home}/.local/share/opencode && cat > ${home}/.local/share/opencode/auth.json << 'AUTHEOF'\n${authJson}\nAUTHEOF`,
|
||||
context,
|
||||
);
|
||||
|
||||
const agentPromptPath = new URL("../agent-prompt.md", import.meta.url);
|
||||
const agentPrompt = readFileSync(agentPromptPath, "utf-8");
|
||||
|
||||
const opencodeConfig = JSON.stringify({
|
||||
model: env.OPENCODE_MODEL,
|
||||
share: "disabled",
|
||||
permission: "allow",
|
||||
agent: {
|
||||
build: {
|
||||
mode: "primary",
|
||||
prompt: agentPrompt,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const configB64 = Buffer.from(opencodeConfig).toString("base64");
|
||||
await exec(
|
||||
sandbox,
|
||||
"write-config",
|
||||
`echo "${configB64}" | base64 -d > ${home}/opencode/opencode.json`,
|
||||
context,
|
||||
);
|
||||
|
||||
const opencodeEnv = this.buildRuntimeEnv();
|
||||
const githubToken = opencodeEnv.GITHUB_TOKEN ?? "";
|
||||
|
||||
logger.info({
|
||||
event: "sandbox.github.auth",
|
||||
component: "sandbox-manager",
|
||||
message: githubToken.length > 0
|
||||
? "Configured authenticated gh CLI in sandbox runtime"
|
||||
: "Running sandbox gh CLI unauthenticated (no GITHUB_TOKEN provided)",
|
||||
threadId,
|
||||
sandboxId,
|
||||
authenticated: githubToken.length > 0,
|
||||
});
|
||||
|
||||
await exec(
|
||||
sandbox,
|
||||
"start-opencode",
|
||||
"setsid opencode serve --port 4096 --hostname 0.0.0.0 > /tmp/opencode.log 2>&1 &",
|
||||
context,
|
||||
{
|
||||
cwd: `${home}/opencode`,
|
||||
env: opencodeEnv,
|
||||
},
|
||||
);
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 3000));
|
||||
|
||||
const preview = await sandbox.getPreviewLink(4096);
|
||||
const previewUrl = preview.url.replace(/\/$/, "");
|
||||
const previewToken = preview.token ?? null;
|
||||
|
||||
const healthy = await waitForHealthy({ previewUrl, previewToken }, 120_000);
|
||||
if (!healthy) {
|
||||
const startupLog = await exec(sandbox, "read-opencode-log", "cat /tmp/opencode.log 2>/dev/null | tail -100", context);
|
||||
throw new Error(`OpenCode server did not become healthy: ${startupLog.slice(0, 400)}`);
|
||||
}
|
||||
|
||||
const sessionId = await createSession({ previewUrl, previewToken }, `Discord thread ${threadId}`);
|
||||
|
||||
const session: SessionInfo = {
|
||||
threadId,
|
||||
channelId,
|
||||
guildId,
|
||||
sandboxId,
|
||||
sessionId,
|
||||
previewUrl,
|
||||
previewToken,
|
||||
status: "active",
|
||||
};
|
||||
|
||||
await this.store.upsert(session);
|
||||
await this.store.markHealthOk(threadId);
|
||||
this.resetTimeout(threadId);
|
||||
|
||||
logger.info({
|
||||
event: "sandbox.create.ready",
|
||||
component: "sandbox-manager",
|
||||
message: "Session is ready",
|
||||
threadId,
|
||||
channelId,
|
||||
guildId,
|
||||
sandboxId,
|
||||
sessionId,
|
||||
durationMs: totalTimer.elapsedMs(),
|
||||
});
|
||||
|
||||
return session;
|
||||
} catch (error) {
|
||||
logger.error({
|
||||
event: "sandbox.create.failed",
|
||||
component: "sandbox-manager",
|
||||
message: "Failed to create session",
|
||||
threadId,
|
||||
channelId,
|
||||
guildId,
|
||||
sandboxId,
|
||||
durationMs: totalTimer.elapsedMs(),
|
||||
error,
|
||||
});
|
||||
|
||||
await this.store.updateStatus(threadId, "error", error instanceof Error ? error.message : String(error)).catch(() => {});
|
||||
await daytona.delete(sandbox).catch(() => {});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async sendMessage(session: SessionInfo, text: string): Promise<string> {
|
||||
return this.withThreadLock(session.threadId, async () => {
|
||||
const t = timer();
|
||||
await this.store.markActivity(session.threadId);
|
||||
this.resetTimeout(session.threadId);
|
||||
|
||||
try {
|
||||
const response = await sendPrompt(
|
||||
{ previewUrl: session.previewUrl, previewToken: session.previewToken },
|
||||
session.sessionId,
|
||||
text,
|
||||
);
|
||||
|
||||
logger.info({
|
||||
event: "session.message.ok",
|
||||
component: "sandbox-manager",
|
||||
message: "Message processed",
|
||||
threadId: session.threadId,
|
||||
sandboxId: session.sandboxId,
|
||||
sessionId: session.sessionId,
|
||||
durationMs: t.elapsedMs(),
|
||||
responseChars: response.length,
|
||||
});
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
const sessionMissing = message.includes("Failed to send prompt (404");
|
||||
const recoverable =
|
||||
message.includes("no IP address found") ||
|
||||
message.includes("Is the Sandbox started") ||
|
||||
message.includes("sandbox not found") ||
|
||||
message.includes("Failed to send prompt (5") ||
|
||||
sessionMissing;
|
||||
|
||||
if (recoverable) {
|
||||
await this.store.incrementResumeFailure(session.threadId, message);
|
||||
if (sessionMissing) {
|
||||
await this.store.updateStatus(session.threadId, "error", "opencode-session-missing").catch(() => {});
|
||||
} else {
|
||||
await this.pauseSessionUnlocked(session.threadId, "recoverable send failure").catch(() => {});
|
||||
}
|
||||
const recoveryError = new Error("SANDBOX_DEAD");
|
||||
(recoveryError as any).recoverable = true;
|
||||
throw recoveryError;
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async pauseSession(threadId: string, reason = "manual"): Promise<void> {
|
||||
await this.withThreadLock(threadId, async () => {
|
||||
await this.pauseSessionUnlocked(threadId, reason);
|
||||
});
|
||||
}
|
||||
|
||||
private async pauseSessionUnlocked(threadId: string, reason: string): Promise<void> {
|
||||
const session = await this.store.getByThread(threadId);
|
||||
if (!session) return;
|
||||
if (session.status === "paused") return;
|
||||
|
||||
await this.store.updateStatus(threadId, "pausing", reason);
|
||||
|
||||
try {
|
||||
const daytona = createDaytona();
|
||||
const sandbox = await daytona.get(session.sandboxId);
|
||||
await daytona.stop(sandbox);
|
||||
await this.store.updateStatus(threadId, "paused", null);
|
||||
this.clearTimeout(threadId);
|
||||
|
||||
logger.info({
|
||||
event: "sandbox.paused",
|
||||
component: "sandbox-manager",
|
||||
message: "Paused sandbox",
|
||||
threadId,
|
||||
sandboxId: session.sandboxId,
|
||||
reason,
|
||||
});
|
||||
} catch (error) {
|
||||
await this.store.updateStatus(threadId, "destroyed", error instanceof Error ? error.message : String(error));
|
||||
logger.warn({
|
||||
event: "sandbox.pause.missing",
|
||||
component: "sandbox-manager",
|
||||
message: "Sandbox unavailable while pausing; marked destroyed",
|
||||
threadId,
|
||||
sandboxId: session.sandboxId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async tryResumeSession(session: SessionInfo): Promise<ResumeAttemptResult> {
|
||||
if (!["paused", "destroyed", "error", "pausing", "resuming"].includes(session.status)) {
|
||||
return { session: null, allowRecreate: true };
|
||||
}
|
||||
|
||||
await this.store.updateStatus(session.threadId, "resuming");
|
||||
|
||||
const daytona = createDaytona();
|
||||
let sandbox: Awaited<ReturnType<typeof daytona.get>>;
|
||||
|
||||
try {
|
||||
sandbox = await daytona.get(session.sandboxId);
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
await this.store.incrementResumeFailure(session.threadId, errorMessage).catch(() => {});
|
||||
await this.store.updateStatus(session.threadId, "destroyed", errorMessage).catch(() => {});
|
||||
|
||||
logger.warn({
|
||||
event: "sandbox.resume.sandbox_missing",
|
||||
component: "sandbox-manager",
|
||||
message: "Sandbox missing during resume; safe to recreate",
|
||||
threadId: session.threadId,
|
||||
sandboxId: session.sandboxId,
|
||||
errorMessage,
|
||||
});
|
||||
|
||||
return { session: null, allowRecreate: true };
|
||||
}
|
||||
|
||||
try {
|
||||
await daytona.start(sandbox, getEnv().SANDBOX_CREATION_TIMEOUT);
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
await this.store.incrementResumeFailure(session.threadId, errorMessage).catch(() => {});
|
||||
await this.store.updateStatus(session.threadId, "error", errorMessage).catch(() => {});
|
||||
|
||||
const allowRecreate = isSandboxMissingError(error);
|
||||
logger.warn({
|
||||
event: "sandbox.resume.start_failed",
|
||||
component: "sandbox-manager",
|
||||
message: allowRecreate
|
||||
? "Sandbox no longer exists while starting; safe to recreate"
|
||||
: "Sandbox start failed; refusing automatic recreate to avoid context loss",
|
||||
threadId: session.threadId,
|
||||
sandboxId: session.sandboxId,
|
||||
errorMessage,
|
||||
allowRecreate,
|
||||
});
|
||||
|
||||
return { session: null, allowRecreate };
|
||||
}
|
||||
|
||||
try {
|
||||
const preview = await sandbox.getPreviewLink(4096);
|
||||
const previewUrl = preview.url.replace(/\/$/, "");
|
||||
const previewToken = preview.token ?? null;
|
||||
|
||||
const context = { threadId: session.threadId, sandboxId: session.sandboxId };
|
||||
|
||||
logger.info({
|
||||
event: "sandbox.resume.restarting_opencode",
|
||||
component: "sandbox-manager",
|
||||
message: "Restarting opencode serve after sandbox start",
|
||||
threadId: session.threadId,
|
||||
sandboxId: session.sandboxId,
|
||||
});
|
||||
|
||||
await exec(
|
||||
sandbox,
|
||||
"restart-opencode-serve",
|
||||
"pkill -f 'opencode serve --port 4096' >/dev/null 2>&1 || true; for d in \"$HOME/opencode\" \"/home/daytona/opencode\" \"/root/opencode\"; do if [ -d \"$d\" ]; then cd \"$d\" && setsid opencode serve --port 4096 --hostname 0.0.0.0 > /tmp/opencode.log 2>&1 & exit 0; fi; done; exit 1",
|
||||
context,
|
||||
{ env: this.buildRuntimeEnv() },
|
||||
);
|
||||
|
||||
const healthy = await waitForHealthy(
|
||||
{ previewUrl, previewToken },
|
||||
getEnv().RESUME_HEALTH_TIMEOUT_MS,
|
||||
);
|
||||
|
||||
if (!healthy) {
|
||||
const startupLog = await exec(
|
||||
sandbox,
|
||||
"read-opencode-log-after-resume",
|
||||
"cat /tmp/opencode.log 2>/dev/null | tail -120",
|
||||
context,
|
||||
).catch(() => "(unable to read opencode log)");
|
||||
|
||||
const errorMessage = `OpenCode health check failed after resume. Log: ${startupLog.slice(0, 500)}`;
|
||||
await this.store.incrementResumeFailure(session.threadId, errorMessage).catch(() => {});
|
||||
await this.store.updateStatus(session.threadId, "error", errorMessage).catch(() => {});
|
||||
|
||||
logger.error({
|
||||
event: "sandbox.resume.health_failed",
|
||||
component: "sandbox-manager",
|
||||
message: "OpenCode did not become healthy after restart; refusing recreate",
|
||||
threadId: session.threadId,
|
||||
sandboxId: session.sandboxId,
|
||||
errorMessage,
|
||||
});
|
||||
|
||||
return { session: null, allowRecreate: false };
|
||||
}
|
||||
|
||||
let sessionId = session.sessionId;
|
||||
const existingSession = await sessionExists({ previewUrl, previewToken }, sessionId);
|
||||
if (!existingSession) {
|
||||
const expectedTitle = `Discord thread ${session.threadId}`;
|
||||
const sessions = await listSessions({ previewUrl, previewToken }, 50).catch(() => []);
|
||||
|
||||
const replacement = sessions
|
||||
.filter((candidate) => candidate.title === expectedTitle)
|
||||
.sort((a, b) => (b.updatedAt ?? 0) - (a.updatedAt ?? 0))[0];
|
||||
|
||||
if (replacement) {
|
||||
sessionId = replacement.id;
|
||||
logger.info({
|
||||
event: "sandbox.resume.session_reused_by_title",
|
||||
component: "sandbox-manager",
|
||||
message: "Reattached to existing session by title",
|
||||
threadId: session.threadId,
|
||||
sandboxId: session.sandboxId,
|
||||
previousSessionId: session.sessionId,
|
||||
sessionId,
|
||||
});
|
||||
} else {
|
||||
logger.warn({
|
||||
event: "sandbox.resume.session_missing",
|
||||
component: "sandbox-manager",
|
||||
message: "OpenCode session missing after resume; creating replacement session",
|
||||
threadId: session.threadId,
|
||||
sandboxId: session.sandboxId,
|
||||
sessionId,
|
||||
});
|
||||
|
||||
sessionId = await createSession({ previewUrl, previewToken }, expectedTitle);
|
||||
}
|
||||
}
|
||||
|
||||
const resumed: SessionInfo = {
|
||||
...session,
|
||||
sessionId,
|
||||
previewUrl,
|
||||
previewToken,
|
||||
status: "active",
|
||||
};
|
||||
|
||||
await this.store.upsert(resumed);
|
||||
await this.store.markHealthOk(session.threadId);
|
||||
this.resetTimeout(session.threadId);
|
||||
|
||||
logger.info({
|
||||
event: "sandbox.resumed",
|
||||
component: "sandbox-manager",
|
||||
message: "Resumed existing sandbox",
|
||||
threadId: session.threadId,
|
||||
sandboxId: session.sandboxId,
|
||||
previousSessionId: session.sessionId,
|
||||
sessionId,
|
||||
sessionReattached: sessionId === session.sessionId,
|
||||
});
|
||||
|
||||
return { session: resumed, allowRecreate: false };
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
await this.store.incrementResumeFailure(session.threadId, errorMessage).catch(() => {});
|
||||
await this.store.updateStatus(session.threadId, "error", errorMessage).catch(() => {});
|
||||
|
||||
logger.warn({
|
||||
event: "sandbox.resume.failed",
|
||||
component: "sandbox-manager",
|
||||
message: "Resume failed after sandbox start; refusing automatic recreate",
|
||||
threadId: session.threadId,
|
||||
sandboxId: session.sandboxId,
|
||||
errorMessage,
|
||||
});
|
||||
|
||||
return { session: null, allowRecreate: false };
|
||||
}
|
||||
}
|
||||
|
||||
async destroySession(threadId: string): Promise<void> {
|
||||
await this.withThreadLock(threadId, async () => {
|
||||
const session = await this.store.getByThread(threadId);
|
||||
if (!session) return;
|
||||
|
||||
await this.store.updateStatus(threadId, "destroying");
|
||||
|
||||
try {
|
||||
const daytona = createDaytona();
|
||||
const sandbox = await daytona.get(session.sandboxId);
|
||||
await daytona.delete(sandbox);
|
||||
} catch {
|
||||
// no-op
|
||||
}
|
||||
|
||||
await this.store.updateStatus(threadId, "destroyed");
|
||||
this.clearTimeout(threadId);
|
||||
});
|
||||
}
|
||||
|
||||
resetTimeout(threadId: string): void {
|
||||
this.clearTimeout(threadId);
|
||||
const timeoutMs = getEnv().SANDBOX_TIMEOUT_MINUTES * 60 * 1000;
|
||||
|
||||
const handle = setTimeout(async () => {
|
||||
timeouts.delete(threadId);
|
||||
await this.pauseSession(threadId, "inactivity-timeout").catch((error) => {
|
||||
logger.error({
|
||||
event: "sandbox.pause.timeout.failed",
|
||||
component: "sandbox-manager",
|
||||
message: "Failed to pause sandbox on inactivity timeout",
|
||||
threadId,
|
||||
error,
|
||||
});
|
||||
});
|
||||
}, timeoutMs);
|
||||
|
||||
timeouts.set(threadId, handle);
|
||||
}
|
||||
|
||||
private clearTimeout(threadId: string): void {
|
||||
const existing = timeouts.get(threadId);
|
||||
if (!existing) return;
|
||||
clearTimeout(existing);
|
||||
timeouts.delete(threadId);
|
||||
}
|
||||
|
||||
startCleanupLoop(): void {
|
||||
const intervalMs = 5 * 60 * 1000;
|
||||
|
||||
this.cleanupInterval = setInterval(async () => {
|
||||
try {
|
||||
const env = getEnv();
|
||||
const staleActive = await this.store.listStaleActive(env.SANDBOX_TIMEOUT_MINUTES + 5);
|
||||
|
||||
for (const session of staleActive) {
|
||||
await this.pauseSession(session.threadId, "cleanup-stale-active");
|
||||
}
|
||||
|
||||
const expiredPaused = await this.store.listExpiredPaused(env.PAUSED_TTL_MINUTES);
|
||||
for (const session of expiredPaused) {
|
||||
await this.destroySession(session.threadId);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error({
|
||||
event: "cleanup.loop.failed",
|
||||
component: "sandbox-manager",
|
||||
message: "Cleanup loop failed",
|
||||
error,
|
||||
});
|
||||
}
|
||||
}, intervalMs);
|
||||
|
||||
logger.info({
|
||||
event: "cleanup.loop.started",
|
||||
component: "sandbox-manager",
|
||||
message: "Started cleanup loop",
|
||||
intervalMs,
|
||||
timeoutMinutes: getEnv().SANDBOX_TIMEOUT_MINUTES,
|
||||
pausedTtlMinutes: getEnv().PAUSED_TTL_MINUTES,
|
||||
});
|
||||
}
|
||||
|
||||
stopCleanupLoop(): void {
|
||||
if (!this.cleanupInterval) return;
|
||||
clearInterval(this.cleanupInterval);
|
||||
this.cleanupInterval = null;
|
||||
}
|
||||
|
||||
async destroyAll(): Promise<void> {
|
||||
const active = await this.store.listActive();
|
||||
await Promise.allSettled(active.map((session) => this.pauseSession(session.threadId, "shutdown")));
|
||||
}
|
||||
|
||||
private buildRuntimeEnv(): Record<string, string> {
|
||||
const env = getEnv();
|
||||
const runtimeEnv: Record<string, string> = {};
|
||||
const githubToken = env.GITHUB_TOKEN.trim();
|
||||
|
||||
if (githubToken.length > 0) {
|
||||
runtimeEnv.GH_TOKEN = githubToken;
|
||||
runtimeEnv.GITHUB_TOKEN = githubToken;
|
||||
}
|
||||
|
||||
return runtimeEnv;
|
||||
}
|
||||
|
||||
private async ensureSessionHealthy(session: SessionInfo, maxWaitMs: number): Promise<boolean> {
|
||||
const healthy = await waitForHealthy(
|
||||
{ previewUrl: session.previewUrl, previewToken: session.previewToken },
|
||||
maxWaitMs,
|
||||
);
|
||||
|
||||
if (!healthy) {
|
||||
await this.store.incrementResumeFailure(session.threadId, "active-session-healthcheck-failed").catch(() => {});
|
||||
await this.store.updateStatus(session.threadId, "error", "active-session-healthcheck-failed").catch(() => {});
|
||||
return false;
|
||||
}
|
||||
|
||||
const attached = await sessionExists(
|
||||
{ previewUrl: session.previewUrl, previewToken: session.previewToken },
|
||||
session.sessionId,
|
||||
).catch(() => false);
|
||||
|
||||
if (!attached) {
|
||||
await this.store.incrementResumeFailure(session.threadId, "active-session-missing").catch(() => {});
|
||||
await this.store.updateStatus(session.threadId, "error", "active-session-missing").catch(() => {});
|
||||
return false;
|
||||
}
|
||||
|
||||
await this.store.markHealthOk(session.threadId).catch(() => {});
|
||||
return true;
|
||||
}
|
||||
|
||||
private async withThreadLock<T>(threadId: string, fn: () => Promise<T>): Promise<T> {
|
||||
const previous = this.threadLocks.get(threadId) ?? Promise.resolve();
|
||||
let release!: () => void;
|
||||
const current = new Promise<void>((resolve) => {
|
||||
release = resolve;
|
||||
});
|
||||
|
||||
this.threadLocks.set(threadId, previous.then(() => current));
|
||||
await previous;
|
||||
|
||||
try {
|
||||
return await fn();
|
||||
} finally {
|
||||
release();
|
||||
if (this.threadLocks.get(threadId) === current) {
|
||||
this.threadLocks.delete(threadId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type { SessionStatus };
|
||||
198
packages/discord/src/sandbox/opencode-client.ts
Normal file
198
packages/discord/src/sandbox/opencode-client.ts
Normal file
@@ -0,0 +1,198 @@
|
||||
import { Effect, Schedule } from "effect";
|
||||
import { logger } from "../observability/logger";
|
||||
|
||||
type PreviewAccess = {
|
||||
previewUrl: string;
|
||||
previewToken?: string | null;
|
||||
};
|
||||
|
||||
export type OpenCodeSessionSummary = {
|
||||
id: string;
|
||||
title: string;
|
||||
updatedAt?: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse a Daytona preview URL into base URL and token.
|
||||
* Preview URLs look like: https://4096-xxx.proxy.daytona.works?tkn=abc123
|
||||
*/
|
||||
function parsePreview(input: string | PreviewAccess): { base: string; token: string | null } {
|
||||
const previewUrl = typeof input === "string" ? input : input.previewUrl;
|
||||
const url = new URL(previewUrl);
|
||||
const token = typeof input === "string"
|
||||
? url.searchParams.get("tkn")
|
||||
: (input.previewToken ?? url.searchParams.get("tkn"));
|
||||
url.searchParams.delete("tkn");
|
||||
return { base: url.toString().replace(/\/$/, ""), token };
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch wrapper that properly handles Daytona preview URL token auth.
|
||||
* Sends token as x-daytona-preview-token header.
|
||||
*/
|
||||
async function previewFetch(preview: string | PreviewAccess, path: string, init?: RequestInit): Promise<Response> {
|
||||
const { base, token } = parsePreview(preview);
|
||||
const url = `${base}${path}`;
|
||||
const headers = new Headers(init?.headers);
|
||||
if (token) {
|
||||
headers.set("x-daytona-preview-token", token);
|
||||
}
|
||||
return fetch(url, { ...init, headers });
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the OpenCode server inside a sandbox to become healthy.
|
||||
* Polls GET /global/health every 2s up to maxWaitMs.
|
||||
*/
|
||||
export async function waitForHealthy(preview: string | PreviewAccess, maxWaitMs = 120_000): Promise<boolean> {
|
||||
const start = Date.now();
|
||||
let lastStatus = "";
|
||||
|
||||
const poll = Effect.tryPromise(async () => {
|
||||
const res = await previewFetch(preview, "/global/health");
|
||||
lastStatus = `${res.status}`;
|
||||
|
||||
if (res.ok) {
|
||||
const body = await res.json() as { healthy?: boolean };
|
||||
if (body.healthy) return true;
|
||||
lastStatus = `200 but healthy=${body.healthy}`;
|
||||
throw new Error(lastStatus);
|
||||
}
|
||||
|
||||
const body = await res.text().catch(() => "");
|
||||
lastStatus = `${res.status}: ${body.slice(0, 150)}`;
|
||||
throw new Error(lastStatus);
|
||||
}).pipe(
|
||||
Effect.tapError(() =>
|
||||
Effect.sync(() => {
|
||||
const elapsed = ((Date.now() - start) / 1000).toFixed(0);
|
||||
logger.warn({
|
||||
event: "opencode.health.poll",
|
||||
component: "opencode-client",
|
||||
message: "Health check poll failed",
|
||||
elapsedSec: Number(elapsed),
|
||||
lastStatus,
|
||||
});
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
const maxAttempts = Math.max(1, Math.ceil(maxWaitMs / 2000));
|
||||
|
||||
return Effect.runPromise(
|
||||
poll.pipe(
|
||||
Effect.retry(
|
||||
Schedule.intersect(
|
||||
Schedule.spaced("2 seconds"),
|
||||
Schedule.recurs(maxAttempts - 1),
|
||||
),
|
||||
),
|
||||
Effect.as(true),
|
||||
Effect.catchAll(() =>
|
||||
Effect.sync(() => {
|
||||
logger.error({
|
||||
event: "opencode.health.failed",
|
||||
component: "opencode-client",
|
||||
message: "Health check failed",
|
||||
maxWaitMs,
|
||||
lastStatus,
|
||||
});
|
||||
return false;
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new OpenCode session and returns the session ID.
|
||||
*/
|
||||
export async function createSession(preview: string | PreviewAccess, title: string): Promise<string> {
|
||||
const res = await previewFetch(preview, "/session", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ title }),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const body = await res.text().catch(() => "");
|
||||
throw new Error(`Failed to create session (${res.status}): ${body}`);
|
||||
}
|
||||
|
||||
const session = await res.json() as { id: string };
|
||||
return session.id;
|
||||
}
|
||||
|
||||
export async function sessionExists(preview: string | PreviewAccess, sessionId: string): Promise<boolean> {
|
||||
const res = await previewFetch(preview, `/session/${sessionId}`, {
|
||||
method: "GET",
|
||||
});
|
||||
|
||||
if (res.ok) return true;
|
||||
if (res.status === 404) return false;
|
||||
|
||||
const body = await res.text().catch(() => "");
|
||||
throw new Error(`Failed to check session (${res.status}): ${body}`);
|
||||
}
|
||||
|
||||
export async function listSessions(preview: string | PreviewAccess, limit = 50): Promise<OpenCodeSessionSummary[]> {
|
||||
const query = limit > 0 ? `?limit=${limit}` : "";
|
||||
const res = await previewFetch(preview, `/session${query}`, {
|
||||
method: "GET",
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const body = await res.text().catch(() => "");
|
||||
throw new Error(`Failed to list sessions (${res.status}): ${body}`);
|
||||
}
|
||||
|
||||
const sessions = await res.json() as Array<{
|
||||
id?: string;
|
||||
title?: string;
|
||||
time?: { updated?: number };
|
||||
}>;
|
||||
|
||||
return sessions
|
||||
.filter((session) => typeof session.id === "string")
|
||||
.map((session) => ({
|
||||
id: session.id as string,
|
||||
title: session.title ?? "",
|
||||
updatedAt: session.time?.updated,
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a prompt to an existing session and returns the text response.
|
||||
* This call blocks until the agent finishes processing.
|
||||
*/
|
||||
export async function sendPrompt(preview: string | PreviewAccess, sessionId: string, text: string): Promise<string> {
|
||||
const res = await previewFetch(preview, `/session/${sessionId}/message`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
parts: [{ type: "text", text }],
|
||||
}),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const body = await res.text().catch(() => "");
|
||||
throw new Error(`Failed to send prompt (${res.status}): ${body}`);
|
||||
}
|
||||
|
||||
const result = await res.json() as { parts?: Array<{ type: string; text?: string; content?: string }> };
|
||||
const parts = result.parts ?? [];
|
||||
|
||||
const textContent = parts
|
||||
.filter((p) => p.type === "text")
|
||||
.map((p) => p.text || p.content || "")
|
||||
.filter(Boolean);
|
||||
|
||||
return textContent.join("\n\n") || "(No response from agent)";
|
||||
}
|
||||
|
||||
/**
|
||||
* Aborts a running session.
|
||||
*/
|
||||
export async function abortSession(preview: string | PreviewAccess, sessionId: string): Promise<void> {
|
||||
await previewFetch(preview, `/session/${sessionId}/abort`, { method: "POST" }).catch(() => {});
|
||||
}
|
||||
266
packages/discord/src/sessions/store.ts
Normal file
266
packages/discord/src/sessions/store.ts
Normal file
@@ -0,0 +1,266 @@
|
||||
import { getSql } from "../db/client";
|
||||
import type { SessionInfo, SessionStatus } from "../types";
|
||||
|
||||
type SessionRow = {
|
||||
thread_id: string;
|
||||
channel_id: string;
|
||||
guild_id: string;
|
||||
sandbox_id: string;
|
||||
session_id: string;
|
||||
preview_url: string;
|
||||
preview_token: string | null;
|
||||
status: SessionStatus;
|
||||
last_error: string | null;
|
||||
resume_fail_count: number;
|
||||
};
|
||||
|
||||
export interface SessionStore {
|
||||
upsert(session: SessionInfo): Promise<void>;
|
||||
getByThread(threadId: string): Promise<SessionInfo | null>;
|
||||
hasTrackedThread(threadId: string): Promise<boolean>;
|
||||
getActive(threadId: string): Promise<SessionInfo | null>;
|
||||
markActivity(threadId: string): Promise<void>;
|
||||
markHealthOk(threadId: string): Promise<void>;
|
||||
updateStatus(threadId: string, status: SessionStatus, lastError?: string | null): Promise<void>;
|
||||
incrementResumeFailure(threadId: string, lastError: string): Promise<void>;
|
||||
listActive(): Promise<SessionInfo[]>;
|
||||
listStaleActive(cutoffMinutes: number): Promise<SessionInfo[]>;
|
||||
listExpiredPaused(pausedTtlMinutes: number): Promise<SessionInfo[]>;
|
||||
}
|
||||
|
||||
class NeonSessionStore implements SessionStore {
|
||||
private readonly sql = getSql();
|
||||
|
||||
async upsert(session: SessionInfo): Promise<void> {
|
||||
await this.sql`
|
||||
INSERT INTO discord_sessions (
|
||||
thread_id,
|
||||
channel_id,
|
||||
guild_id,
|
||||
sandbox_id,
|
||||
session_id,
|
||||
preview_url,
|
||||
preview_token,
|
||||
status,
|
||||
last_error,
|
||||
last_activity,
|
||||
resumed_at,
|
||||
created_at,
|
||||
updated_at
|
||||
) VALUES (
|
||||
${session.threadId},
|
||||
${session.channelId},
|
||||
${session.guildId},
|
||||
${session.sandboxId},
|
||||
${session.sessionId},
|
||||
${session.previewUrl},
|
||||
${session.previewToken},
|
||||
${session.status},
|
||||
${session.lastError ?? null},
|
||||
NOW(),
|
||||
CASE WHEN ${session.status} = 'active' THEN NOW() ELSE NULL END,
|
||||
NOW(),
|
||||
NOW()
|
||||
)
|
||||
ON CONFLICT (thread_id)
|
||||
DO UPDATE SET
|
||||
channel_id = EXCLUDED.channel_id,
|
||||
guild_id = EXCLUDED.guild_id,
|
||||
sandbox_id = EXCLUDED.sandbox_id,
|
||||
session_id = EXCLUDED.session_id,
|
||||
preview_url = EXCLUDED.preview_url,
|
||||
preview_token = EXCLUDED.preview_token,
|
||||
status = EXCLUDED.status,
|
||||
last_error = EXCLUDED.last_error,
|
||||
last_activity = NOW(),
|
||||
resumed_at = CASE WHEN EXCLUDED.status = 'active' THEN NOW() ELSE discord_sessions.resumed_at END,
|
||||
updated_at = NOW()
|
||||
`;
|
||||
}
|
||||
|
||||
async getByThread(threadId: string): Promise<SessionInfo | null> {
|
||||
const rows = await this.sql`
|
||||
SELECT
|
||||
thread_id,
|
||||
channel_id,
|
||||
guild_id,
|
||||
sandbox_id,
|
||||
session_id,
|
||||
preview_url,
|
||||
preview_token,
|
||||
status,
|
||||
last_error,
|
||||
resume_fail_count
|
||||
FROM discord_sessions
|
||||
WHERE thread_id = ${threadId}
|
||||
LIMIT 1
|
||||
` as SessionRow[];
|
||||
|
||||
if (rows.length === 0) return null;
|
||||
return toSessionInfo(rows[0]);
|
||||
}
|
||||
|
||||
async hasTrackedThread(threadId: string): Promise<boolean> {
|
||||
const rows = await this.sql`
|
||||
SELECT thread_id
|
||||
FROM discord_sessions
|
||||
WHERE thread_id = ${threadId}
|
||||
LIMIT 1
|
||||
` as Array<{ thread_id: string }>;
|
||||
|
||||
return rows.length > 0;
|
||||
}
|
||||
|
||||
async getActive(threadId: string): Promise<SessionInfo | null> {
|
||||
const rows = await this.sql`
|
||||
SELECT
|
||||
thread_id,
|
||||
channel_id,
|
||||
guild_id,
|
||||
sandbox_id,
|
||||
session_id,
|
||||
preview_url,
|
||||
preview_token,
|
||||
status,
|
||||
last_error,
|
||||
resume_fail_count
|
||||
FROM discord_sessions
|
||||
WHERE thread_id = ${threadId}
|
||||
AND status = 'active'
|
||||
LIMIT 1
|
||||
` as SessionRow[];
|
||||
|
||||
if (rows.length === 0) return null;
|
||||
return toSessionInfo(rows[0]);
|
||||
}
|
||||
|
||||
async markActivity(threadId: string): Promise<void> {
|
||||
await this.sql`
|
||||
UPDATE discord_sessions
|
||||
SET last_activity = NOW(), updated_at = NOW()
|
||||
WHERE thread_id = ${threadId}
|
||||
`;
|
||||
}
|
||||
|
||||
async markHealthOk(threadId: string): Promise<void> {
|
||||
await this.sql`
|
||||
UPDATE discord_sessions
|
||||
SET last_health_ok_at = NOW(), updated_at = NOW()
|
||||
WHERE thread_id = ${threadId}
|
||||
`;
|
||||
}
|
||||
|
||||
async updateStatus(threadId: string, status: SessionStatus, lastError?: string | null): Promise<void> {
|
||||
await this.sql`
|
||||
UPDATE discord_sessions
|
||||
SET
|
||||
status = ${status},
|
||||
last_error = ${lastError ?? null},
|
||||
pause_requested_at = CASE WHEN ${status} = 'pausing' THEN NOW() ELSE pause_requested_at END,
|
||||
paused_at = CASE WHEN ${status} = 'paused' THEN NOW() ELSE paused_at END,
|
||||
resume_attempted_at = CASE WHEN ${status} = 'resuming' THEN NOW() ELSE resume_attempted_at END,
|
||||
resumed_at = CASE WHEN ${status} = 'active' THEN NOW() ELSE resumed_at END,
|
||||
destroyed_at = CASE WHEN ${status} = 'destroyed' THEN NOW() ELSE destroyed_at END,
|
||||
updated_at = NOW()
|
||||
WHERE thread_id = ${threadId}
|
||||
`;
|
||||
}
|
||||
|
||||
async incrementResumeFailure(threadId: string, lastError: string): Promise<void> {
|
||||
await this.sql`
|
||||
UPDATE discord_sessions
|
||||
SET
|
||||
resume_fail_count = resume_fail_count + 1,
|
||||
last_error = ${lastError},
|
||||
updated_at = NOW()
|
||||
WHERE thread_id = ${threadId}
|
||||
`;
|
||||
}
|
||||
|
||||
async listActive(): Promise<SessionInfo[]> {
|
||||
const rows = await this.sql`
|
||||
SELECT
|
||||
thread_id,
|
||||
channel_id,
|
||||
guild_id,
|
||||
sandbox_id,
|
||||
session_id,
|
||||
preview_url,
|
||||
preview_token,
|
||||
status,
|
||||
last_error,
|
||||
resume_fail_count
|
||||
FROM discord_sessions
|
||||
WHERE status = 'active'
|
||||
ORDER BY last_activity DESC
|
||||
` as SessionRow[];
|
||||
|
||||
return rows.map(toSessionInfo);
|
||||
}
|
||||
|
||||
async listStaleActive(cutoffMinutes: number): Promise<SessionInfo[]> {
|
||||
const rows = await this.sql`
|
||||
SELECT
|
||||
thread_id,
|
||||
channel_id,
|
||||
guild_id,
|
||||
sandbox_id,
|
||||
session_id,
|
||||
preview_url,
|
||||
preview_token,
|
||||
status,
|
||||
last_error,
|
||||
resume_fail_count
|
||||
FROM discord_sessions
|
||||
WHERE status = 'active'
|
||||
AND last_activity < NOW() - (${cutoffMinutes} || ' minutes')::interval
|
||||
ORDER BY last_activity ASC
|
||||
` as SessionRow[];
|
||||
|
||||
return rows.map(toSessionInfo);
|
||||
}
|
||||
|
||||
async listExpiredPaused(pausedTtlMinutes: number): Promise<SessionInfo[]> {
|
||||
const rows = await this.sql`
|
||||
SELECT
|
||||
thread_id,
|
||||
channel_id,
|
||||
guild_id,
|
||||
sandbox_id,
|
||||
session_id,
|
||||
preview_url,
|
||||
preview_token,
|
||||
status,
|
||||
last_error,
|
||||
resume_fail_count
|
||||
FROM discord_sessions
|
||||
WHERE status = 'paused'
|
||||
AND paused_at IS NOT NULL
|
||||
AND paused_at < NOW() - (${pausedTtlMinutes} || ' minutes')::interval
|
||||
ORDER BY paused_at ASC
|
||||
` as SessionRow[];
|
||||
|
||||
return rows.map(toSessionInfo);
|
||||
}
|
||||
}
|
||||
|
||||
function toSessionInfo(row: SessionRow): SessionInfo {
|
||||
return {
|
||||
threadId: row.thread_id,
|
||||
channelId: row.channel_id,
|
||||
guildId: row.guild_id,
|
||||
sandboxId: row.sandbox_id,
|
||||
sessionId: row.session_id,
|
||||
previewUrl: row.preview_url,
|
||||
previewToken: row.preview_token,
|
||||
status: row.status,
|
||||
lastError: row.last_error,
|
||||
resumeFailCount: row.resume_fail_count,
|
||||
};
|
||||
}
|
||||
|
||||
const sessionStore: SessionStore = new NeonSessionStore();
|
||||
|
||||
export function getSessionStore(): SessionStore {
|
||||
return sessionStore;
|
||||
}
|
||||
22
packages/discord/src/types.ts
Normal file
22
packages/discord/src/types.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
export type SessionStatus =
|
||||
| "creating"
|
||||
| "active"
|
||||
| "pausing"
|
||||
| "paused"
|
||||
| "resuming"
|
||||
| "destroying"
|
||||
| "destroyed"
|
||||
| "error";
|
||||
|
||||
export interface SessionInfo {
|
||||
threadId: string;
|
||||
channelId: string;
|
||||
guildId: string;
|
||||
sandboxId: string;
|
||||
sessionId: string;
|
||||
previewUrl: string;
|
||||
previewToken: string | null;
|
||||
status: SessionStatus;
|
||||
lastError?: string | null;
|
||||
resumeFailCount?: number;
|
||||
}
|
||||
18
packages/discord/tsconfig.json
Normal file
18
packages/discord/tsconfig.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ES2022",
|
||||
"moduleResolution": "bundler",
|
||||
"lib": ["ES2022", "DOM"],
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"sourceMap": true,
|
||||
"outDir": "./dist",
|
||||
"types": ["bun-types", "node"]
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
Reference in New Issue
Block a user