History

init

Rhys Sullivan committed 9 months ago

Commit 29fe127No parent (first commit)33 files · +2632 −0Browse files

Unified · Split

33 files
  1. .context/openclaw +1 −0
  2. .context/opensrc-mcp +1 −0
  3. .gitignore +36 −0
  4. AGENTS.md +1 −0
  5. CLAUDE.md +120 −0
  6. OPENASSISTANT_HIGH_LEVEL_PLAN.md +309 −0
  7. OPENASSISTANT_MVP_PRIMITIVES.md +324 −0
  8. README.md +15 −0
  9. apps/discord-bot/package.json +22 −0
  10. apps/discord-bot/src/approval-bridge.ts +109 −0
  11. apps/discord-bot/src/approval-registry.test.ts +38 −0
  12. apps/discord-bot/src/approval-registry.ts +67 −0
  13. apps/discord-bot/src/calendar-store.ts +32 −0
  14. apps/discord-bot/src/codegen.test.ts +77 −0
  15. apps/discord-bot/src/codegen.ts +322 −0
  16. apps/discord-bot/src/format-response.ts +61 −0
  17. apps/discord-bot/src/index.ts +102 −0
  18. apps/discord-bot/src/tools.ts +22 −0
  19. apps/discord-bot/tsconfig.json +9 −0
  20. apps/gateway/package.json +21 −0
  21. apps/gateway/src/index.ts +10 −0
  22. apps/gateway/tsconfig.json +9 −0
  23. bun.lock +384 −0
  24. index.ts +1 −0
  25. package.json +30 −0
  26. packages/core/package.json +17 −0
  27. packages/core/src/codemode/runner.test.ts +117 −0
  28. packages/core/src/codemode/runner.ts +318 −0
  29. packages/core/src/index.ts +4 −0
  30. packages/core/tsconfig.json +9 −0
  31. tsconfig.base.json +22 −0
  32. tsconfig.json +14 −0
  33. vitest.config.ts +8 −0
.context/openclaw+1−0
@@ -0,0 +1 @@
1+Subproject commit 6f4665dda3af787c50685e440cca21a57816b5a6
.context/opensrc-mcp+1−0
@@ -0,0 +1 @@
1+Subproject commit 43c45b2cbe0e64fb7859164a28e29da427c37930
.gitignore+36−0
@@ -0,0 +1,36 @@
1+# dependencies (bun install)
2+node_modules
3+
4+# output
5+out
6+dist
7+*.tgz
8+
9+# code coverage
10+coverage
11+*.lcov
12+
13+# logs
14+logs
15+_.log
16+report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
17+
18+# dotenv environment variable files
19+.env
20+.env.production
21+.env.development.local
22+.env.test.local
23+.env.production.local
24+.env.local
25+
26+# caches
27+.eslintcache
28+.cache
29+*.tsbuildinfo
30+.reference/
31+
32+# IntelliJ based IDEs
33+.idea
34+
35+# Finder (MacOS) folder config
36+.DS_Store
AGENTS.md+1−0
@@ -0,0 +1 @@
1+CLAUDE.md
CLAUDE.md+120−0
@@ -0,0 +1,120 @@
1+
2+Default to using Bun instead of Node.js.
3+
4+- Use `bun <file>` instead of `node <file>` or `ts-node <file>`
5+- Use `bun test` instead of `jest` or `vitest`
6+- Use `bun build <file.html|file.ts|file.css>` instead of `webpack` or `esbuild`
7+- Use `bun install` instead of `npm install` or `yarn install` or `pnpm install`
8+- Use `bun run <script>` instead of `npm run <script>` or `yarn run <script>` or `pnpm run <script>`
9+- Use `bunx <package> <command>` instead of `npx <package> <command>`
10+- Bun automatically loads .env, so don't use dotenv.
11+
12+## APIs
13+
14+- `Bun.serve()` supports WebSockets, HTTPS, and routes. Don't use `express`.
15+- `bun:sqlite` for SQLite. Don't use `better-sqlite3`.
16+- `Bun.redis` for Redis. Don't use `ioredis`.
17+- `Bun.sql` for Postgres. Don't use `pg` or `postgres.js`.
18+- `WebSocket` is built-in. Don't use `ws`.
19+- Prefer `Bun.file` over `node:fs`'s readFile/writeFile
20+- Bun.$`ls` instead of execa.
21+
22+## Testing
23+
24+Use `bun test` to run tests.
25+
26+```ts#index.test.ts
27+import { test, expect } from "bun:test";
28+
29+test("hello world", () => {
30+ expect(1).toBe(1);
31+});
32+```
33+
34+## Frontend
35+
36+Use HTML imports with `Bun.serve()`. Don't use `vite`. HTML imports fully support React, CSS, Tailwind.
37+
38+Server:
39+
40+```ts#index.ts
41+import index from "./index.html"
42+
43+Bun.serve({
44+ routes: {
45+ "/": index,
46+ "/api/users/:id": {
47+ GET: (req) => {
48+ return new Response(JSON.stringify({ id: req.params.id }));
49+ },
50+ },
51+ },
52+ // optional websocket support
53+ websocket: {
54+ open: (ws) => {
55+ ws.send("Hello, world!");
56+ },
57+ message: (ws, message) => {
58+ ws.send(message);
59+ },
60+ close: (ws) => {
61+ // handle close
62+ }
63+ },
64+ development: {
65+ hmr: true,
66+ console: true,
67+ }
68+})
69+```
70+
71+HTML files can import .tsx, .jsx or .js files directly and Bun's bundler will transpile & bundle automatically. `<link>` tags can point to stylesheets and Bun's CSS bundler will bundle.
72+
73+```html#index.html
74+<html>
75+ <body>
76+ <h1>Hello, world!</h1>
77+ <script type="module" src="./frontend.tsx"></script>
78+ </body>
79+</html>
80+```
81+
82+With the following `frontend.tsx`:
83+
84+```tsx#frontend.tsx
85+import React from "react";
86+import { createRoot } from "react-dom/client";
87+
88+// import .css files directly and it works
89+import './index.css';
90+
91+const root = createRoot(document.body);
92+
93+export default function Frontend() {
94+ return <h1>Hello, world!</h1>;
95+}
96+
97+root.render(<Frontend />);
98+```
99+
100+Then, run index.ts
101+
102+```sh
103+bun --hot ./index.ts
104+```
105+
106+For more information, read the Bun API docs in `node_modules/bun-types/docs/**.mdx`.
107+
108+<!-- effect-solutions:start -->
109+## Effect Best Practices
110+
111+**IMPORTANT:** Always consult effect-solutions before writing Effect code.
112+
113+1. Run `effect-solutions list` to see available guides
114+2. Run `effect-solutions show <topic>...` for relevant patterns (supports multiple topics)
115+3. Search `.reference/effect/` for real implementations (run `effect-solutions setup` first)
116+
117+Topics: quick-start, project-setup, tsconfig, basics, services-and-layers, data-modeling, error-handling, config, testing, cli.
118+
119+Never guess at Effect patterns - check the guide first.
120+<!-- effect-solutions:end -->
OPENASSISTANT_HIGH_LEVEL_PLAN.md+309−0
@@ -0,0 +1,309 @@
1+# OpenAssistant: High-Level Architecture Plan
2+
3+## 1) Product Thesis
4+
5+Build a personal agent platform with OpenClaw-like flexibility, but with a strict trust model:
6+
7+- No free-form shell as the default execution surface.
8+- Actions run through typed TypeScript clients with explicit capability boundaries.
9+- Destructive operations require approval by policy.
10+- Every claim from the model is backed by auditable execution evidence.
11+
12+## 2) Core Principles
13+
14+- Capability-first execution: the model can only call registered client methods.
15+- Policy as code: permissions, approval gates, and risk tiers are explicit config.
16+- Evidence over narration: the assistant response is generated from execution receipts.
17+- Durable automation: reminders/schedules are owned by scheduler state, not model memory.
18+- Portable runtime: Bun + TypeScript monorepo, predictable local and remote deploy paths.
19+
20+## 3) What To Keep vs Replace
21+
22+Keep from OpenClaw:
23+
24+- Gateway + session architecture.
25+- Multi-client model (Discord, CLI, web, device nodes).
26+- Event stream for system and execution updates.
27+- Cron/heartbeat concept for always-on automation.
28+
29+Replace:
30+
31+- Bash-first action model.
32+- Implicit tool use visibility.
33+- Broad account credentials without fine-grained scope boundaries.
34+
35+## 4) System Shape
36+
37+Control plane:
38+
39+- `gateway`: authoritative orchestration service.
40+- `policy-engine`: evaluates if a call is auto-allowed, approval-required, or denied.
41+- `approval-service`: manages pending approvals, TTL, decisions, audit trail.
42+- `scheduler`: durable jobs, reminders, retries, dead-letter.
43+- `event-log`: append-only event stream and query API for UI and audits.
44+
45+Execution plane:
46+
47+- `code-runner`: executes generated TypeScript in sandboxed runtime.
48+- `client-runtime`: injects typed clients into script context.
49+- `tool-host-local`: local client execution with local creds.
50+- `tool-host-remote`: remote tool execution with scoped ephemeral creds.
51+
52+Interaction plane:
53+
54+- `chat-clients`: Discord/web/CLI channels.
55+- `ops-ui`: approvals, receipts, schedules, policy edits, run timelines.
56+
57+## 5) Execution Model (Codemode + Typed Clients)
58+
59+Single model-facing tool:
60+
61+- `run_code({ code, intent, sessionId })`.
62+
63+Injected runtime in script:
64+
65+- `clients.<integration>.<method>(input)`
66+- `Result<T, E>` return style.
67+- No `child_process`, no `fetch` directly, no unrestricted FS/network.
68+
69+Example shape (conceptual):
70+
71+```ts
72+const result = await clients.calendar.updateEvent({ id, patch })
73+if (result.isError()) {
74+ return Result.err(result.error)
75+}
76+return Result.ok(result.value)
77+```
78+
79+Outcome:
80+
81+- OpenClaw-level composability.
82+- MCP/tool-call-style control boundaries.
83+- Better determinism and auditability than shell command synthesis.
84+
85+## 6) Capability and Permission Model
86+
87+Permission unit:
88+
89+- `integration.resource.action` (example: `vercel.firewall.rule.delete`).
90+
91+Client method metadata:
92+
93+- `risk`: `read | write | destructive | privileged`.
94+- `autoRun`: boolean default.
95+- `requiresApproval`: optional override.
96+- `idempotent`: boolean.
97+- `allowedScopes`: account/project/environment constraints.
98+
99+Policy decision:
100+
101+- `allow` if method scope and risk pass policy.
102+- `ask` if action is sensitive or outside auto-run envelope.
103+- `deny` if forbidden by capability map.
104+
105+## 7) Approval Flow
106+
107+Request path:
108+
109+1. Script calls client method.
110+2. Runtime asks policy engine.
111+3. If `ask`, execution pauses with `ApprovalPending` receipt.
112+4. Approval prompt sent to origin chat + optional ops targets.
113+5. Approver chooses `allow-once`, `allow-window`, `deny`, or `allow-and-policy-update`.
114+6. Execution resumes or returns denial as value.
115+
116+Key UX requirements:
117+
118+- User always sees which exact method and arguments are pending.
119+- Expiration and timeout reason are explicit.
120+- Run status updates: `pending`, `running`, `completed`, `denied`, `failed`.
121+
122+## 8) Receipts and Anti-Lying Guarantees
123+
124+Every side-effectful call emits immutable receipts:
125+
126+- `call.requested`
127+- `call.approved|call.denied`
128+- `call.started`
129+- `call.finished|call.failed`
130+
131+Assistant response rule:
132+
133+- For external actions, summarize from receipts only.
134+- If no receipt exists, assistant must state action was not completed.
135+
136+UI requirement:
137+
138+- Inline "did it really run?" evidence panel per assistant answer.
139+
140+## 9) Scheduler and Reminders
141+
142+Scheduler owns state, not the LLM:
143+
144+- Reminders become scheduler jobs with a stable `jobId`.
145+- Jobs are persisted in a durable store.
146+- Delivery attempts and outcomes are logged as receipts.
147+- If delivery target fails, retries/backoff are deterministic.
148+
149+Contract:
150+
151+- "Remind me tomorrow at 9am" returns a created job receipt immediately.
152+- User can query, edit, pause, or delete by `jobId`.
153+
154+## 10) OpenAPI to Client Pipeline
155+
156+Goal:
157+
158+- Convert API specs into safe typed clients and policy metadata.
159+
160+Pipeline:
161+
162+1. Ingest OpenAPI spec.
163+2. Generate TypeScript client methods.
164+3. Classify methods by risk heuristics and optional provider-specific rules.
165+4. Emit package with runtime validators + method metadata.
166+5. Register integration in gateway catalog.
167+
168+Outputs per integration package:
169+
170+- `client.ts` typed method surface.
171+- `schema.ts` input/output validation.
172+- `policy.ts` default risk/approval metadata.
173+- `README.md` capability list and example usage.
174+
175+## 11) Remote Tools and Ephemeral Credentials
176+
177+Remote execution model:
178+
179+- Gateway requests short-lived scoped token from credential broker.
180+- Tool host executes only permitted client methods.
181+- Token TTL and scope are embedded in receipt for audit.
182+
183+Security baseline:
184+
185+- No long-lived broad API keys in model runtime.
186+- Per-tool/per-method scope limits.
187+- Revocation and rotation built into credential broker.
188+
189+## 12) Monorepo Layout (Bun)
190+
191+```text
192+apps/
193+ gateway/
194+ ops-ui/
195+ discord-client/
196+ cli/
197+packages/
198+ sdk/
199+ runner/
200+ policy-engine/
201+ approval-service/
202+ scheduler/
203+ event-log/
204+ receipt-model/
205+ integration-registry/
206+ integration-openapi-gen/
207+ integrations/
208+ posthog/
209+ github/
210+ vercel/
211+ gcal/
212+ transport-ws/
213+ transport-http/
214+ shared-types/
215+```
216+
217+## 13) Data Model (High-Level)
218+
219+Core entities:
220+
221+- `Session`
222+- `Run`
223+- `Script`
224+- `ToolCall`
225+- `ApprovalRequest`
226+- `Receipt`
227+- `Job`
228+- `CredentialLease`
229+- `PolicySnapshot`
230+
231+Strong recommendation:
232+
233+- Event-sourced append log for execution and approvals.
234+- Materialized read views for chat timeline and dashboard.
235+
236+## 14) MVP Scope (Phase 1)
237+
238+Include:
239+
240+- Gateway + WS event stream.
241+- Code runner with sandboxed TS execution.
242+- Typed integration runtime (manual first: GitHub + Calendar + PostHog).
243+- Policy engine with `read/write/destructive`.
244+- Approval UI in Discord + lightweight web panel.
245+- Scheduler with reminders and hourly monitors.
246+- Receipt-backed final answer generation.
247+
248+Exclude initially:
249+
250+- General bash execution.
251+- Arbitrary plugin execution from untrusted sources.
252+- Complex multi-tenant org model.
253+
254+## 15) Phase Plan
255+
256+Phase A: Foundation
257+
258+- Monorepo skeleton, shared types, event bus, gateway sessions.
259+- Runner sandbox and `run_code` API.
260+
261+Phase B: Trust Layer
262+
263+- Policy engine, approvals, receipt model, auditable run timeline.
264+
265+Phase C: Integrations
266+
267+- Manual clients + openapi codegen pipeline.
268+- Scoped credentials and remote tool host.
269+
270+Phase D: Automation
271+
272+- Scheduler, durable reminders, monitors, retry semantics.
273+
274+Phase E: UX
275+
276+- Discord + web ops surface for approvals and evidence links.
277+
278+## 16) Key Risks and Mitigations
279+
280+Risk: Model-generated TS can still be unsafe.
281+Mitigation: hard sandbox + no direct network/process APIs + capability-injected clients only.
282+
283+Risk: Approval fatigue.
284+Mitigation: risk tiers, approval windows, per-integration trusted scopes.
285+
286+Risk: "Says it did it" regressions.
287+Mitigation: receipt-backed response contract + explicit uncertainty language.
288+
289+Risk: Credential sprawl.
290+Mitigation: brokered ephemeral tokens and strict method-level scopes.
291+
292+## 17) First Technical Decision Set
293+
294+Decide now:
295+
296+- Bun runtime boundary for runner isolation.
297+- Storage stack for append log + views.
298+- Event transport protocol (WS-first, HTTP fallback).
299+- Policy DSL format (JSON policy vs TS policy modules).
300+- Credential broker interface and lease format.
301+
302+## 18) Success Criteria
303+
304+You should be able to do all of the following with evidence:
305+
306+- "Monitor PostHog and ping me at 10M" (job created, triggered, delivered).
307+- "Check Vercel anomaly email and advise" (reads logged, recommendation traceable).
308+- "Change firewall rule" (approval prompted, approved actor recorded, call receipt linked).
309+- "Remind me tomorrow at 9" (deterministic scheduler receipt, guaranteed delivery attempts).
OPENASSISTANT_MVP_PRIMITIVES.md+324−0
@@ -0,0 +1,324 @@
1+# OpenAssistant MVP: Primitive-First Plan
2+
3+## Goal
4+
5+Ship an MVP that is:
6+
7+- Small enough to build fast.
8+- Safe enough to trust.
9+- Extensible enough to add integrations later.
10+
11+Core rule: the system does not guarantee outcomes, it guarantees evidence of attempted/completed calls to integrations.
12+
13+## Non-Goals (MVP)
14+
15+- No default reminder/calendar system.
16+- No giant plugin marketplace.
17+- No many-package architecture upfront.
18+- No bash-first execution path.
19+
20+## MVP Building Blocks (Primitives)
21+
22+### 1) `Run Primitive`
23+
24+Executes model-generated TypeScript in a sandbox with a single injected surface:
25+
26+- `tools.<integration>.<method>(input): Result<ok, err>`
27+
28+No direct shell/network/process access from model code.
29+
30+### 2) `Tool Contract Primitive`
31+
32+Each tool method has metadata:
33+
34+- `id`: `integration.resource.action`
35+- `risk`: `read | write | destructive`
36+- `requiresApproval`: boolean or policy-driven
37+- `schema`: input/output validation
38+
39+This is the capability boundary.
40+
41+### 3) `Policy + Approval Primitive`
42+
43+Policy returns one of:
44+
45+- `allow`
46+- `ask`
47+- `deny`
48+
49+If `ask`, run pauses until explicit decision (`allow-once`, `deny`, optional short-lived window).
50+
51+### 4) `Receipt Primitive` (Proof of Work)
52+
53+Append-only receipts for every tool interaction:
54+
55+- `tool.call.requested`
56+- `tool.call.approved | tool.call.denied` (if applicable)
57+- `tool.call.started`
58+- `tool.call.succeeded | tool.call.failed`
59+
60+These receipts are the source of truth for what actually happened.
61+
62+### 5) `Relevant Tool Activity Primitive` (Evidence Surface)
63+
64+The gateway derives a system-generated summary of relevant tool activity from receipts.
65+
66+- This summary is passed to the model for final response generation.
67+- This same summary is surfaced to the user/client.
68+- The summary is filtered to relevant calls, not a full noisy log.
69+
70+Summary fields (MVP):
71+
72+- `toolId`
73+- `status` (`succeeded | failed | denied | pending`)
74+- `approval` (`required/decision` when applicable)
75+- `when` (timestamp)
76+- `receiptRef` (id/link for audit trail)
77+
78+Relevance rules (MVP):
79+
80+- Include calls from the current run.
81+- Always include failed/denied/pending calls.
82+- Include side-effectful calls (`write | destructive`).
83+- Optionally include key reads used to justify decisions.
84+
85+This is the anti-lying mechanism: responses are grounded in gateway-produced evidence summaries, not free model claims.
86+
87+### 6) `Response Grounding Primitive`
88+
89+Assistant action statements should be based on the relevant tool activity summary.
90+
91+If no successful call exists for an intended action, response must say it could not verify completion.
92+
93+### 7) `Tool Workspace Primitive` (User-Authored Tools)
94+
95+Users (or the agent) can create tools in an assistant workspace directory.
96+
97+- Tool files are TypeScript modules using a shared `defineTool()` contract.
98+- Each tool exports metadata, schema, and handler.
99+- Gateway discovers and reloads tools from the workspace on file change or explicit reload.
100+
101+This enables "write your own tools" without touching core runtime code.
102+
103+### 8) `Auth Profile Primitive` (Claude Max Support)
104+
105+MVP supports Anthropic Claude Max the same way OpenClaw does:
106+
107+- User runs `claude setup-token` outside the app.
108+- User pastes token into OpenAssistant auth command.
109+- Token is stored as an auth profile of `type: "token"` for provider `anthropic`.
110+- Model runs resolve credentials from profile order first, then env fallback.
111+
112+This avoids coupling to Claude CLI internals while still leveraging Claude Max plan auth.
113+
114+## Minimal Repository Shape
115+
116+Keep it simple first:
117+
118+```text
119+apps/
120+ gateway/ # chat loop, run orchestration, ws/http API
121+ discord-client/ # primary MVP chat + approval interaction surface
122+packages/
123+ core/ # shared types, Result, receipt models, policy types
124+ runtime/ # TS runner sandbox + tool bridge
125+ sdk/ # defineTool(), integration client wrappers
126+integrations/
127+ example-calendar/
128+ example-posthog/
129+```
130+
131+Only 3 packages. Add more only when pain is real.
132+
133+Notes:
134+
135+- `ops` means operator surface (approvals + receipts), not necessarily a web app.
136+- MVP should use Discord as the operator surface first to avoid UI overhead.
137+- Optional web ops UI can be phase 2.
138+
139+## Data Model (MVP)
140+
141+- `Run { id, sessionId, status, startedAt, endedAt }`
142+- `ToolCall { id, runId, toolId, inputHash, risk }`
143+- `ApprovalRequest { id, toolCallId, status, decidedBy, decidedAt }`
144+- `Receipt { id, runId, toolCallId?, type, ts, payload }`
145+- `RelevantToolActivity { runId, items[] }` (derived view from receipts)
146+- `AuthProfile { id, provider, type, secretRef, expiresAt?, lastUsedAt?, cooldownUntil? }`
147+
148+Use one append-only receipts table/log first. Build read views later.
149+
150+## How “Reminder Truthfulness” Works Without Built-In Reminder System
151+
152+User says: “Remind me tomorrow at 9.”
153+
154+MVP behavior:
155+
156+1. Model chooses an integration tool (calendar/todo/automation/etc).
157+2. Runtime executes that tool call.
158+3. Receipt(s) are written.
159+4. Gateway derives relevant tool activity summary.
160+5. Assistant response + client surface that summary.
161+
162+So:
163+
164+- If scheduling tool call is `succeeded`, response can say it was scheduled and show that tool call.
165+- If scheduling call is `failed/denied/pending`, response should say that and surface the relevant call.
166+
167+No assumption about which reminder provider is used.
168+
169+## User-Authored Tool Flow (MVP)
170+
171+Assistant workspace (example):
172+
173+```text
174+~/.config/openassistant/
175+ tools/
176+ posthog/
177+ client.ts
178+ tools.ts
179+```
180+
181+Tool definition shape (conceptual):
182+
183+```ts
184+export const checkVisitors = defineTool({
185+ id: "posthog.analytics.getVisitors",
186+ risk: "read",
187+ input: z.object({ projectId: z.string(), website: z.string() }),
188+ policy: { default: "allow" },
189+ handler: async (input, ctx) => { /* call posthog */ },
190+})
191+
192+export const createThresholdMonitor = defineTool({
193+ id: "posthog.monitor.createThresholdJob",
194+ risk: "write",
195+ input: z.object({
196+ website: z.string(),
197+ threshold: z.number(),
198+ cron: z.string(), // e.g. daily 9am
199+ }),
200+ policy: { default: "ask" },
201+ handler: async (input, ctx) => { /* call scheduler or remote endpoint */ },
202+})
203+```
204+
205+Runtime flow:
206+
207+1. Prompt asks for PostHog threshold monitor.
208+2. Agent writes/updates PostHog tool module in workspace.
209+3. Gateway reloads tool registry.
210+4. Run uses newly available tool methods.
211+5. Every call emits receipts.
212+6. Gateway computes relevant tool activity.
213+7. Assistant + client surface relevant tool activity.
214+
215+What is guaranteed:
216+
217+- Proof that the tool endpoint was called and what happened.
218+
219+What is not guaranteed by core:
220+
221+- Which reminder/scheduler provider is used internally by that tool.
222+
223+## Claude Max Compatibility (OpenClaw Pattern)
224+
225+### Storage
226+
227+Auth store file (example):
228+
229+```text
230+~/.config/openassistant/auth-profiles.json
231+```
232+
233+Credential shape:
234+
235+```ts
236+type AuthCredential =
237+ | { type: "api_key"; provider: string; key: string }
238+ | { type: "token"; provider: string; token: string; expires?: number }
239+ | { type: "oauth"; provider: string; access: string; refresh: string; expires: number }
240+```
241+
242+For Claude Max setup-token:
243+
244+- write profile like `anthropic:manual` with `type: "token"`.
245+- mark config profile mode as `token` (oauth-compatible for provider auth logic).
246+
247+### CLI Surface (MVP)
248+
249+```bash
250+openassistant auth anthropic setup-token
251+```
252+
253+Flow:
254+
255+1. Prompt user to run `claude setup-token`.
256+2. Validate token prefix/shape.
257+3. Save token profile in auth store.
258+4. Set profile as preferred for Anthropic provider in config/order.
259+
260+### Provider Auth Resolution
261+
262+Resolution order:
263+
264+1. Explicit profile override (if run/session pinned).
265+2. Provider profile order from config/store.
266+3. Env fallback (`ANTHROPIC_OAUTH_TOKEN`, then `ANTHROPIC_API_KEY`).
267+4. Error with actionable re-auth instructions.
268+
269+### Runtime Behavior
270+
271+At run start:
272+
273+- Resolve provider auth once for selected model/provider.
274+- Inject credential into model client adapter.
275+
276+On provider failures (rate-limit/auth/billing):
277+
278+- mark profile failure/cooldown.
279+- rotate to next profile when available.
280+- emit receipt/event that profile rotation occurred.
281+
282+This gives Claude Max usability plus long-term multi-profile resilience.
283+
284+## Extensibility Path
285+
286+New integration = add one folder under `integrations/*` implementing:
287+
288+- Tool contract metadata
289+- Runtime client wrapper
290+- Schema validation
291+
292+No core refactor needed if primitives are stable.
293+
294+## MVP Scope Checklist
295+
296+- Sandbox run primitive working.
297+- Tool contract registry working.
298+- Workspace tool discovery/reload working.
299+- Policy/approval gate working.
300+- Receipt log working and queryable.
301+- Relevant tool activity summary derivation working.
302+- Response grounding from relevant tool activity working.
303+- Anthropic setup-token auth flow working (`claude setup-token` paste path).
304+- Provider auth resolver supports profile order + env fallback.
305+- Discord client handles chat + approvals + receipt links.
306+- 2-3 integrations to prove composability.
307+
308+## Suggested Order (Build Sequence)
309+
310+1. `core`: types + receipt/event schema + policy decision enums.
311+2. `runtime`: sandbox executor + injected tool bridge.
312+3. `gateway`: run orchestration + tool registry + persistence + evidence summarization.
313+4. `discord-client`: chat loop + approve/deny actions + receipt viewing links.
314+5. `integrations`: add calendar/posthog/github as examples.
315+
316+## Success Criteria
317+
318+You can reliably demonstrate:
319+
320+- A destructive action pauses for approval and logs full receipt trail.
321+- A non-destructive read auto-runs and logs receipt trail.
322+- Assistant response includes relevant tool activity for claims it makes.
323+- Assistant cannot claim tool success when relevant activity has no success state.
324+- A reminder-style request is only “done” when an integration success receipt exists.
README.md+15−0
@@ -0,0 +1,15 @@
1+# assistant
2+
3+To install dependencies:
4+
5+```bash
6+bun install
7+```
8+
9+To run:
10+
11+```bash
12+bun run index.ts
13+```
14+
15+This project was created using `bun init` in bun v1.3.5. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.
apps/discord-bot/package.json+22−0
@@ -0,0 +1,22 @@
1+{
2+ "name": "@openassistant/discord-bot",
3+ "private": true,
4+ "type": "module",
5+ "scripts": {
6+ "with-env": "bun run --env-file=../../.env --",
7+ "with-env:prod": "bun run --env-file=../../.env.production --",
8+ "dev": "bun with-env bun run src/index.ts",
9+ "start": "bun with-env bun run src/index.ts",
10+ "start:prod": "bun with-env:prod bun run src/index.ts",
11+ "typecheck": "tsgo --noEmit --incremental false -p tsconfig.json",
12+ "clean": "bun -e \"import { rmSync } from 'node:fs'; for (const p of ['dist','node_modules','.turbo','coverage','.tsbuildinfo']) rmSync(p,{recursive:true,force:true});\""
13+ },
14+ "dependencies": {
15+ "@openassistant/core": "workspace:*",
16+ "ai": "^6.0.73",
17+ "ai-sdk-provider-claude-code": "^3.3.6",
18+ "discord.js": "^14.17.3",
19+ "effect": "latest",
20+ "zod": "^4.3.6"
21+ }
22+}
apps/discord-bot/src/approval-bridge.ts+109−0
@@ -0,0 +1,109 @@
1+import type { ApprovalDecision, ApprovalRequest } from "@openassistant/core";
2+import {
3+ ActionRowBuilder,
4+ ButtonBuilder,
5+ ButtonStyle,
6+ type ButtonInteraction,
7+ type MessageCreateOptions,
8+} from "discord.js";
9+import { ApprovalRegistry } from "./approval-registry.js";
10+
11+const CUSTOM_ID_PREFIX = "oa.approval";
12+
13+export class DiscordApprovalBridge {
14+ private readonly registry: ApprovalRegistry;
15+
16+ constructor(timeoutMs: number = 5 * 60_000) {
17+ this.registry = new ApprovalRegistry(timeoutMs);
18+ }
19+
20+ async requestApproval(params: {
21+ request: ApprovalRequest;
22+ channel: { send: (message: MessageCreateOptions) => Promise<unknown> };
23+ requesterId: string;
24+ }): Promise<ApprovalDecision> {
25+ const { request, channel, requesterId } = params;
26+ const pending = this.registry.open(request.callId, requesterId);
27+
28+ try {
29+ const message: MessageCreateOptions = {
30+ content: [
31+ `Approval needed for \`${request.toolPath}\``,
32+ `Call ID: \`${request.callId}\``,
33+ request.inputPreview ? `Input: \`${request.inputPreview}\`` : null,
34+ "Only the requesting user can approve or deny.",
35+ ]
36+ .filter(Boolean)
37+ .join("\n"),
38+ components: [approvalButtons(request.callId)],
39+ };
40+
41+ await channel.send(message);
42+ } catch {
43+ this.registry.cancel(request.callId, "denied");
44+ }
45+
46+ return pending;
47+ }
48+
49+ async handleInteraction(interaction: ButtonInteraction): Promise<boolean> {
50+ const parsed = parseCustomId(interaction.customId);
51+ if (!parsed) {
52+ return false;
53+ }
54+
55+ const decision: ApprovalDecision = parsed.action === "approve" ? "approved" : "denied";
56+ const status = this.registry.resolve(parsed.callId, interaction.user.id, decision);
57+
58+ if (status === "unauthorized") {
59+ await interaction.reply({
60+ content: "Only the requesting user can resolve this approval.",
61+ ephemeral: true,
62+ });
63+ return true;
64+ }
65+
66+ if (status === "not_found") {
67+ await interaction.reply({
68+ content: "This approval is no longer pending.",
69+ ephemeral: true,
70+ });
71+ return true;
72+ }
73+
74+ await interaction.update({
75+ content: `Approval ${decision} by <@${interaction.user.id}> for \`${parsed.callId}\`.`,
76+ components: [],
77+ });
78+ return true;
79+ }
80+}
81+
82+function approvalButtons(callId: string): ActionRowBuilder<ButtonBuilder> {
83+ return new ActionRowBuilder<ButtonBuilder>().addComponents(
84+ new ButtonBuilder()
85+ .setCustomId(`${CUSTOM_ID_PREFIX}:${callId}:approve`)
86+ .setLabel("Approve")
87+ .setStyle(ButtonStyle.Success),
88+ new ButtonBuilder()
89+ .setCustomId(`${CUSTOM_ID_PREFIX}:${callId}:deny`)
90+ .setLabel("Deny")
91+ .setStyle(ButtonStyle.Danger),
92+ );
93+}
94+
95+function parseCustomId(customId: string): { callId: string; action: "approve" | "deny" } | null {
96+ const parts = customId.split(":");
97+ if (parts.length !== 3 || parts[0] !== CUSTOM_ID_PREFIX) {
98+ return null;
99+ }
100+ const callId = parts[1];
101+ if (!callId) {
102+ return null;
103+ }
104+ const action = parts[2];
105+ if (action !== "approve" && action !== "deny") {
106+ return null;
107+ }
108+ return { callId, action };
109+}
apps/discord-bot/src/approval-registry.test.ts+38−0
@@ -0,0 +1,38 @@
1+import { describe, expect, it } from "vitest";
2+import { ApprovalRegistry } from "./approval-registry.js";
3+
4+describe("ApprovalRegistry", () => {
5+ it("resolves for requester", async () => {
6+ const registry = new ApprovalRegistry(1_000);
7+ const pending = registry.open("call_1", "user_a");
8+
9+ const result = registry.resolve("call_1", "user_a", "approved");
10+ const decision = await pending;
11+
12+ expect(result).toBe("resolved");
13+ expect(decision).toBe("approved");
14+ expect(registry.size()).toBe(0);
15+ });
16+
17+ it("rejects unauthorized actor", async () => {
18+ const registry = new ApprovalRegistry(1_000);
19+ const pending = registry.open("call_2", "user_a");
20+
21+ const unauthorized = registry.resolve("call_2", "user_b", "approved");
22+ expect(unauthorized).toBe("unauthorized");
23+ expect(registry.size()).toBe(1);
24+
25+ registry.cancel("call_2");
26+ const decision = await pending;
27+ expect(decision).toBe("denied");
28+ });
29+
30+ it("times out to denied", async () => {
31+ const registry = new ApprovalRegistry(10);
32+ const pending = registry.open("call_3", "user_a");
33+
34+ const decision = await pending;
35+ expect(decision).toBe("denied");
36+ expect(registry.size()).toBe(0);
37+ });
38+});
apps/discord-bot/src/approval-registry.ts+67−0
@@ -0,0 +1,67 @@
1+import type { ApprovalDecision } from "@openassistant/core";
2+
3+type PendingApproval = {
4+ requesterId: string;
5+ resolve: (decision: ApprovalDecision) => void;
6+ timeout: ReturnType<typeof setTimeout>;
7+};
8+
9+export type ResolveApprovalResult = "resolved" | "not_found" | "unauthorized";
10+
11+export class ApprovalRegistry {
12+ private readonly pending = new Map<string, PendingApproval>();
13+
14+ constructor(private readonly timeoutMs: number = 5 * 60_000) {}
15+
16+ open(callId: string, requesterId: string): Promise<ApprovalDecision> {
17+ if (this.pending.has(callId)) {
18+ throw new Error(`Approval already pending: ${callId}`);
19+ }
20+
21+ return new Promise<ApprovalDecision>((resolve) => {
22+ const timeout = setTimeout(() => {
23+ this.finish(callId, "denied");
24+ }, this.timeoutMs);
25+
26+ this.pending.set(callId, {
27+ requesterId,
28+ resolve,
29+ timeout,
30+ });
31+ });
32+ }
33+
34+ resolve(callId: string, actorId: string, decision: ApprovalDecision): ResolveApprovalResult {
35+ const entry = this.pending.get(callId);
36+ if (!entry) {
37+ return "not_found";
38+ }
39+ if (entry.requesterId !== actorId) {
40+ return "unauthorized";
41+ }
42+
43+ this.finish(callId, decision);
44+ return "resolved";
45+ }
46+
47+ cancel(callId: string, decision: ApprovalDecision = "denied"): void {
48+ if (!this.pending.has(callId)) {
49+ return;
50+ }
51+ this.finish(callId, decision);
52+ }
53+
54+ size(): number {
55+ return this.pending.size;
56+ }
57+
58+ private finish(callId: string, decision: ApprovalDecision): void {
59+ const entry = this.pending.get(callId);
60+ if (!entry) {
61+ return;
62+ }
63+ this.pending.delete(callId);
64+ clearTimeout(entry.timeout);
65+ entry.resolve(decision);
66+ }
67+}
apps/discord-bot/src/calendar-store.ts+32−0
@@ -0,0 +1,32 @@
1+export interface CalendarEvent {
2+ id: string;
3+ title: string;
4+ startsAt: string;
5+ notes?: string;
6+}
7+
8+export interface CalendarUpdateInput {
9+ title: string;
10+ startsAt: string;
11+ notes?: string;
12+}
13+
14+export class InMemoryCalendarStore {
15+ private readonly events = new Map<string, CalendarEvent>();
16+
17+ update(input: CalendarUpdateInput): CalendarEvent {
18+ const id = `evt_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
19+ const event: CalendarEvent = {
20+ id,
21+ title: input.title,
22+ startsAt: input.startsAt,
23+ ...(input.notes ? { notes: input.notes } : {}),
24+ };
25+ this.events.set(id, event);
26+ return event;
27+ }
28+
29+ list(): CalendarEvent[] {
30+ return [...this.events.values()];
31+ }
32+}
apps/discord-bot/src/codegen.test.ts+77−0
@@ -0,0 +1,77 @@
1+import { describe, expect, it } from "vitest";
2+import { generateCodeFromPrompt } from "./codegen.js";
3+
4+describe("generateCodeFromPrompt", () => {
5+ it("maps calendar prompts with heuristic provider", async () => {
6+ const now = new Date("2026-02-06T10:00:00.000Z");
7+ const generated = await generateCodeFromPrompt(
8+ "update my calendar to put dinner with ella at 5 pm",
9+ { now, provider: "heuristic" },
10+ );
11+
12+ expect(generated.provider).toBe("heuristic");
13+ expect(generated.code).toContain("tools.calendar.update");
14+ expect(generated.code.toLowerCase()).toContain("dinner with ella");
15+ expect(generated.rationale).toContain("calendar.update");
16+ });
17+
18+ it("supports direct code mode via code prefix", async () => {
19+ const generated = await generateCodeFromPrompt("code: return 42;", {
20+ provider: "heuristic",
21+ });
22+ expect(generated.code).toBe("return 42;");
23+ expect(generated.rationale).toContain("direct code");
24+ });
25+
26+ it("parses structured output from claude provider", async () => {
27+ const generated = await generateCodeFromPrompt("make an event", {
28+ provider: "claude",
29+ generateWithClaude: async () => ({
30+ code: "const event = await tools.calendar.update({ title: 'Dinner', startsAt: '2026-02-06T17:00:00.000Z' }); return { event };",
31+ rationale: "Mapped user prompt to calendar.update",
32+ }),
33+ });
34+
35+ expect(generated.provider).toBe("claude");
36+ expect(generated.code).toContain("tools.calendar.update");
37+ expect(generated.rationale).toContain("Mapped");
38+ });
39+
40+ it("handles multi-line calendar lists with relative days", async () => {
41+ const now = new Date("2026-02-06T10:00:00.000Z"); // Friday
42+ const prompt = [
43+ "Please add the following to my calendar:",
44+ "",
45+ "Dinner 5pm tomorrow",
46+ "",
47+ "Lunch 2 pm sunday",
48+ "",
49+ "Breakfast 3 am Monday",
50+ ].join("\n");
51+
52+ const generated = await generateCodeFromPrompt(prompt, {
53+ now,
54+ provider: "heuristic",
55+ });
56+
57+ expect(generated.code.match(/tools\.calendar\.update\(/g)?.length).toBe(3);
58+ expect(generated.code).toContain('"title":"Dinner"');
59+ expect(generated.code).toContain('"title":"Lunch"');
60+ expect(generated.code).toContain('"title":"Breakfast"');
61+ expect(generated.code).toContain('return { message: "Calendar updated", events };');
62+
63+ const startsAt = [...generated.code.matchAll(/"startsAt":"([^"]+)"/g)]
64+ .map((match) => match[1])
65+ .filter((value): value is string => typeof value === "string");
66+ expect(startsAt).toHaveLength(3);
67+ if (startsAt.length !== 3) {
68+ throw new Error("expected 3 startsAt values");
69+ }
70+ const dinnerAt = new Date(startsAt[0]!);
71+ const lunchAt = new Date(startsAt[1]!);
72+ const breakfastAt = new Date(startsAt[2]!);
73+ expect(dinnerAt.getDay()).toBe(6); // Saturday
74+ expect(lunchAt.getDay()).toBe(0); // Sunday
75+ expect(breakfastAt.getDay()).toBe(1); // Monday
76+ });
77+});
apps/discord-bot/src/codegen.ts+322−0
@@ -0,0 +1,322 @@
1+import { generateObject } from "ai";
2+import { createClaudeCode } from "ai-sdk-provider-claude-code";
3+import { z } from "zod";
4+
5+export interface GeneratedCode {
6+ code: string;
7+ rationale: string;
8+ provider: "claude" | "heuristic";
9+}
10+
11+interface CodegenOptions {
12+ now?: Date;
13+ provider?: "claude" | "heuristic";
14+ generateWithClaude?: (params: { prompt: string; now: Date }) => Promise<{ code: string; rationale: string }>;
15+}
16+
17+const DEFAULT_PROVIDER = resolveDefaultProvider();
18+const DEFAULT_CLAUDE_MODEL = readEnv("OPENASSISTANT_CLAUDE_MODEL")?.trim() || "sonnet";
19+const CLAUDE_TIMEOUT_MS = Number(readEnv("OPENASSISTANT_CLAUDE_TIMEOUT_MS") ?? 30_000);
20+const CLAUDE_PATH = readEnv("OPENASSISTANT_CLAUDE_PATH")?.trim();
21+const CODEGEN_SCHEMA = z.object({
22+ code: z.string().min(1),
23+ rationale: z.string().min(1),
24+});
25+
26+const claudeCode = createClaudeCode({
27+ defaultSettings: {
28+ permissionMode: "dontAsk",
29+ tools: [],
30+ maxTurns: 1,
31+ ...(CLAUDE_PATH ? { pathToClaudeCodeExecutable: CLAUDE_PATH } : {}),
32+ },
33+});
34+
35+export async function generateCodeFromPrompt(
36+ prompt: string,
37+ options: CodegenOptions = {},
38+): Promise<GeneratedCode> {
39+ const now = options.now ?? new Date();
40+ const provider = options.provider ?? DEFAULT_PROVIDER;
41+
42+ if (provider === "claude") {
43+ try {
44+ const generated = await (options.generateWithClaude ?? generateWithClaude)({
45+ prompt,
46+ now,
47+ });
48+ return { ...generated, provider: "claude" };
49+ } catch (error) {
50+ const fallback = generateHeuristic(prompt, now);
51+ return {
52+ ...fallback,
53+ provider: "heuristic",
54+ rationale: `${fallback.rationale} Claude fallback: ${describeUnknown(error)}`,
55+ };
56+ }
57+ }
58+
59+ const generated = generateHeuristic(prompt, now);
60+ return { ...generated, provider: "heuristic" };
61+}
62+
63+async function generateWithClaude(params: {
64+ prompt: string;
65+ now: Date;
66+}): Promise<Omit<GeneratedCode, "provider">> {
67+ const { prompt, now } = params;
68+ const abortController = new AbortController();
69+ const timeout = setTimeout(() => abortController.abort(), CLAUDE_TIMEOUT_MS);
70+ try {
71+ const result = await generateObject({
72+ model: claudeCode(DEFAULT_CLAUDE_MODEL),
73+ schema: CODEGEN_SCHEMA,
74+ prompt: buildPlannerPrompt(prompt, now),
75+ temperature: 0,
76+ abortSignal: abortController.signal,
77+ });
78+
79+ return result.object;
80+ } finally {
81+ clearTimeout(timeout);
82+ }
83+}
84+
85+function buildPlannerPrompt(userPrompt: string, now: Date): string {
86+ return [
87+ "You are a code generator for a Bun TypeScript codemode runner.",
88+ "Return a function body only (no markdown, no fences, no explanations in code).",
89+ "The runtime invokes: new AsyncFunction('tools', code).",
90+ "Available tools:",
91+ "- await tools.calendar.update({ title: string, startsAt: string, notes?: string })",
92+ "- await tools.calendar.list()",
93+ "Rules:",
94+ "- Use await for tool calls.",
95+ "- If request implies creating/updating a calendar event, call tools.calendar.update.",
96+ "- startsAt must be an ISO-8601 string.",
97+ "- If time/date missing, use current timestamp.",
98+ "- Always return a final object (e.g. { message, event }).",
99+ `Current timestamp: ${now.toISOString()}`,
100+ `User request: ${userPrompt}`,
101+ ].join("\n");
102+}
103+
104+function resolveDefaultProvider(): "claude" | "heuristic" {
105+ const configured = readEnv("OPENASSISTANT_CODEGEN_PROVIDER")?.trim().toLowerCase();
106+ if (configured === "heuristic") {
107+ return "heuristic";
108+ }
109+ return "claude";
110+}
111+
112+function generateHeuristic(prompt: string, now: Date): Omit<GeneratedCode, "provider"> {
113+ const trimmed = prompt.trim();
114+ if (!trimmed) {
115+ return {
116+ code: "return { message: 'Empty prompt.' };",
117+ rationale: "Empty prompt fallback.",
118+ };
119+ }
120+
121+ if (trimmed.toLowerCase().startsWith("code:")) {
122+ return {
123+ code: trimmed.slice(5).trim(),
124+ rationale: "User provided direct code.",
125+ };
126+ }
127+
128+ const lower = trimmed.toLowerCase();
129+ if (lower.includes("calendar") || lower.includes("event") || lower.includes("schedule")) {
130+ const updates = extractCalendarUpdates(trimmed, now);
131+ if (updates.length > 0) {
132+ const code = buildCalendarCode(updates);
133+ return {
134+ code,
135+ rationale:
136+ updates.length === 1
137+ ? "Mapped prompt to calendar.update tool call."
138+ : `Mapped prompt to ${updates.length} calendar.update tool calls.`,
139+ };
140+ }
141+ }
142+
143+ return {
144+ code: `return { message: "No tool mapping yet for prompt", prompt: ${JSON.stringify(trimmed)} };`,
145+ rationale: "Fallback response when no tool intent is detected.",
146+ };
147+}
148+
149+function inferTitle(prompt: string): string {
150+ const match = prompt.match(
151+ /(?:put|add|create)\s+(?:an?\s+)?(?:calendar\s+)?(?:event\s+)?(?:to\s+)?(.+?)(?:\s+at\s+|$)/i,
152+ );
153+ if (match?.[1]) {
154+ return cleanTitle(match[1]);
155+ }
156+ return "Untitled event";
157+}
158+
159+function cleanTitle(input: string): string {
160+ return input.replace(/^my\s+/i, "").replace(/^calendar\s+/i, "").trim();
161+}
162+
163+function inferStartTime(prompt: string, now: Date): string {
164+ const time = prompt.match(/(?:\bat\s+)?(\d{1,2})(?::(\d{2}))?\s*(am|pm)\b/i);
165+ if (!time) {
166+ return now.toISOString();
167+ }
168+
169+ const dayToken = prompt.match(
170+ /\b(today|tomorrow|monday|tuesday|wednesday|thursday|friday|saturday|sunday)\b/i,
171+ )?.[1];
172+ return resolveStartsAt(now, Number(time[1]), time[2] ? Number(time[2]) : 0, time[3], dayToken);
173+}
174+
175+function describeUnknown(error: unknown): string {
176+ if (error instanceof Error) {
177+ return error.message;
178+ }
179+ return String(error);
180+}
181+
182+function readEnv(key: string): string | undefined {
183+ const bun = (globalThis as { Bun?: { env?: Record<string, string | undefined> } }).Bun;
184+ return bun?.env?.[key] ?? process.env[key];
185+}
186+
187+type CalendarUpdateInput = {
188+ title: string;
189+ startsAt: string;
190+ notes: string;
191+};
192+
193+const WEEKDAY_INDEX: Record<string, number> = {
194+ sunday: 0,
195+ monday: 1,
196+ tuesday: 2,
197+ wednesday: 3,
198+ thursday: 4,
199+ friday: 5,
200+ saturday: 6,
201+};
202+
203+function buildCalendarCode(updates: CalendarUpdateInput[]): string {
204+ const lines: string[] = ["const events = [];"];
205+ for (const update of updates) {
206+ lines.push(`events.push(await tools.calendar.update(${JSON.stringify(update)}));`);
207+ }
208+ lines.push('return { message: "Calendar updated", events };');
209+ return lines.join(" ");
210+}
211+
212+function extractCalendarUpdates(prompt: string, now: Date): CalendarUpdateInput[] {
213+ const lines = prompt
214+ .split(/\r?\n/)
215+ .map((line) => line.trim())
216+ .filter((line) => line.length > 0);
217+
218+ const updates: CalendarUpdateInput[] = [];
219+ for (const line of lines) {
220+ if (isInstructionLine(line)) {
221+ continue;
222+ }
223+ const parsed = parseCalendarLine(line, now, prompt);
224+ if (parsed) {
225+ updates.push(parsed);
226+ }
227+ }
228+
229+ if (updates.length > 0) {
230+ return updates;
231+ }
232+
233+ return [
234+ {
235+ title: inferTitle(prompt),
236+ startsAt: inferStartTime(prompt, now),
237+ notes: `Requested via Discord prompt: ${prompt}`,
238+ },
239+ ];
240+}
241+
242+function isInstructionLine(line: string): boolean {
243+ const value = line.toLowerCase();
244+ if (value.includes("calendar") && (value.includes("add") || value.includes("following"))) {
245+ return true;
246+ }
247+ return value.startsWith("please ");
248+}
249+
250+function parseCalendarLine(
251+ rawLine: string,
252+ now: Date,
253+ fullPrompt: string,
254+): CalendarUpdateInput | null {
255+ const line = rawLine.replace(/^[-*]\s*/, "").replace(/^\d+[.)]\s*/, "").trim();
256+ const match = line.match(
257+ /^(?<title>.+?)\s+(?:at\s+)?(?<hour>\d{1,2})(?::(?<minute>\d{2}))?\s*(?<meridiem>am|pm)\b(?:\s+(?<day>today|tomorrow|monday|tuesday|wednesday|thursday|friday|saturday|sunday))?$/i,
258+ );
259+
260+ if (!match?.groups) {
261+ return null;
262+ }
263+
264+ const titleRaw = match.groups.title;
265+ const hourRaw = match.groups.hour;
266+ const meridiemRaw = match.groups.meridiem;
267+ if (!titleRaw || !hourRaw || !meridiemRaw) {
268+ return null;
269+ }
270+
271+ const title = cleanTitle(titleRaw);
272+ if (!title) {
273+ return null;
274+ }
275+
276+ const hour = Number(hourRaw);
277+ const minute = match.groups.minute ? Number(match.groups.minute) : 0;
278+ const meridiem = meridiemRaw;
279+ const dayToken = match.groups.day;
280+ const startsAt = resolveStartsAt(now, hour, minute, meridiem, dayToken);
281+
282+ return {
283+ title,
284+ startsAt,
285+ notes: `Requested via Discord prompt: ${fullPrompt}`,
286+ };
287+}
288+
289+function resolveStartsAt(
290+ now: Date,
291+ hour12: number,
292+ minute: number,
293+ meridiemRaw: string | undefined,
294+ dayTokenRaw: string | undefined,
295+): string {
296+ const meridiem = meridiemRaw?.toLowerCase();
297+ const dayToken = dayTokenRaw?.toLowerCase();
298+ let hour = hour12;
299+
300+ if (meridiem === "pm" && hour < 12) {
301+ hour += 12;
302+ } else if (meridiem === "am" && hour === 12) {
303+ hour = 0;
304+ }
305+
306+ const date = new Date(now);
307+ if (dayToken === "tomorrow") {
308+ date.setDate(date.getDate() + 1);
309+ } else if (dayToken) {
310+ const target = WEEKDAY_INDEX[dayToken];
311+ if (target === undefined) {
312+ date.setHours(hour, minute, 0, 0);
313+ return date.toISOString();
314+ }
315+ const current = date.getDay();
316+ const delta = (target - current + 7) % 7;
317+ date.setDate(date.getDate() + delta);
318+ }
319+
320+ date.setHours(hour, minute, 0, 0);
321+ return date.toISOString();
322+}
apps/discord-bot/src/format-response.ts+61−0
@@ -0,0 +1,61 @@
1+import type { CodeModeRunResult } from "@openassistant/core";
2+
3+export function formatDiscordResponse(params: {
4+ prompt: string;
5+ generatedCode: string;
6+ rationale: string;
7+ provider: "claude" | "heuristic";
8+ result: CodeModeRunResult;
9+}): string {
10+ const { prompt, generatedCode, rationale, provider, result } = params;
11+ const sections = [
12+ `Prompt: ${prompt}`,
13+ `Planner (${provider}): ${rationale}`,
14+ "Code:",
15+ fenced("ts", generatedCode),
16+ result.ok ? `Result: ${safeJson(result.value)}` : `Error: ${result.error}`,
17+ "Receipts:",
18+ formatReceipts(result.receipts),
19+ ];
20+
21+ return truncateDiscord(sections.join("\n\n"));
22+}
23+
24+function formatReceipts(receipts: CodeModeRunResult["receipts"]): string {
25+ if (receipts.length === 0) {
26+ return "- none";
27+ }
28+ return receipts
29+ .map((receipt) => {
30+ const parts = [
31+ `- \`${receipt.toolPath}\``,
32+ `decision=${receipt.decision}`,
33+ `status=${receipt.status}`,
34+ ];
35+ if (receipt.inputPreview) {
36+ parts.push(`input="${receipt.inputPreview}"`);
37+ }
38+ return parts.join(" ");
39+ })
40+ .join("\n");
41+}
42+
43+function safeJson(value: unknown): string {
44+ try {
45+ return JSON.stringify(value);
46+ } catch {
47+ return String(value);
48+ }
49+}
50+
51+function fenced(lang: string, content: string): string {
52+ return `\`\`\`${lang}\n${content}\n\`\`\``;
53+}
54+
55+function truncateDiscord(value: string): string {
56+ const limit = 1_900;
57+ if (value.length <= limit) {
58+ return value;
59+ }
60+ return `${value.slice(0, limit)}\n...`;
61+}
apps/discord-bot/src/index.ts+102−0
@@ -0,0 +1,102 @@
1+import { createCodeModeRunner } from "@openassistant/core";
2+import {
3+ ChannelType,
4+ Client,
5+ Events,
6+ GatewayIntentBits,
7+ type Message,
8+ type MessageCreateOptions,
9+ Partials,
10+} from "discord.js";
11+import { Effect } from "effect";
12+import { DiscordApprovalBridge } from "./approval-bridge.js";
13+import { InMemoryCalendarStore } from "./calendar-store.js";
14+import { generateCodeFromPrompt } from "./codegen.js";
15+import { formatDiscordResponse } from "./format-response.js";
16+import { createToolTree } from "./tools.js";
17+
18+const token = Bun.env.DISCORD_BOT_TOKEN;
19+if (!token) {
20+ throw new Error("Missing DISCORD_BOT_TOKEN");
21+}
22+
23+const approvalTimeoutMs = Number(Bun.env.OPENASSISTANT_APPROVAL_TIMEOUT_MS ?? 300_000);
24+
25+const client = new Client({
26+ intents: [
27+ GatewayIntentBits.Guilds,
28+ GatewayIntentBits.GuildMessages,
29+ GatewayIntentBits.DirectMessages,
30+ GatewayIntentBits.MessageContent,
31+ ],
32+ partials: [Partials.Channel],
33+});
34+
35+const approvalBridge = new DiscordApprovalBridge(approvalTimeoutMs);
36+const calendarStore = new InMemoryCalendarStore();
37+const tools = createToolTree(calendarStore);
38+
39+client.once(Events.ClientReady, (readyClient) => {
40+ console.log(`[discord-bot] logged in as ${readyClient.user.tag}`);
41+});
42+
43+client.on(Events.InteractionCreate, async (interaction) => {
44+ if (!interaction.isButton()) {
45+ return;
46+ }
47+ await approvalBridge.handleInteraction(interaction);
48+});
49+
50+client.on(Events.MessageCreate, async (message) => {
51+ if (shouldIgnore(message)) {
52+ return;
53+ }
54+ await handleMessage(message);
55+});
56+
57+await client.login(token);
58+
59+function shouldIgnore(message: Message): boolean {
60+ return message.author.bot || message.content.trim().length === 0 || message.channel.type !== ChannelType.DM;
61+}
62+
63+async function handleMessage(message: Message): Promise<void> {
64+ const ack = await message.reply("Working on it...");
65+ const approvalChannel = message.channel as unknown;
66+ if (
67+ !approvalChannel ||
68+ typeof approvalChannel !== "object" ||
69+ !("send" in approvalChannel) ||
70+ typeof (approvalChannel as { send?: unknown }).send !== "function"
71+ ) {
72+ await ack.edit("This channel does not support approval prompts.");
73+ return;
74+ }
75+
76+ const generated = await generateCodeFromPrompt(message.content);
77+
78+ const runner = createCodeModeRunner({
79+ tools,
80+ requestApproval: (request) =>
81+ Effect.tryPromise({
82+ try: () =>
83+ approvalBridge.requestApproval({
84+ request,
85+ channel: approvalChannel as { send: (message: MessageCreateOptions) => Promise<unknown> },
86+ requesterId: message.author.id,
87+ }),
88+ catch: (error) => error,
89+ }),
90+ });
91+
92+ const result = await Effect.runPromise(runner.run({ code: generated.code }));
93+ const response = formatDiscordResponse({
94+ prompt: message.content,
95+ generatedCode: generated.code,
96+ rationale: generated.rationale,
97+ provider: generated.provider,
98+ result,
99+ });
100+
101+ await ack.edit(response);
102+}
apps/discord-bot/src/tools.ts+22−0
@@ -0,0 +1,22 @@
1+import { defineTool, type ToolTree } from "@openassistant/core";
2+import { Effect } from "effect";
3+import type { InMemoryCalendarStore } from "./calendar-store.js";
4+
5+export function createToolTree(calendarStore: InMemoryCalendarStore): ToolTree {
6+ return {
7+ calendar: {
8+ update: defineTool({
9+ kind: "write",
10+ approval: "required",
11+ run: (input: { title: string; startsAt: string; notes?: string }) =>
12+ Effect.sync(() => calendarStore.update(input)),
13+ previewInput: (input) => `${input.title} @ ${input.startsAt}`,
14+ }),
15+ list: defineTool({
16+ kind: "read",
17+ approval: "auto",
18+ run: () => Effect.sync(() => calendarStore.list()),
19+ }),
20+ },
21+ };
22+}
apps/discord-bot/tsconfig.json+9−0
@@ -0,0 +1,9 @@
1+{
2+ "extends": "../../tsconfig.base.json",
3+ "compilerOptions": {
4+ "rootDir": "src"
5+ },
6+ "include": [
7+ "src/**/*.ts"
8+ ]
9+}
apps/gateway/package.json+21−0
@@ -0,0 +1,21 @@
1+{
2+ "name": "@openassistant/gateway",
3+ "private": true,
4+ "type": "module",
5+ "exports": {
6+ ".": "./src/index.ts"
7+ },
8+ "scripts": {
9+ "with-env": "bun run --env-file=../../.env --",
10+ "with-env:prod": "bun run --env-file=../../.env.production --",
11+ "dev": "bun with-env bun run src/index.ts",
12+ "start": "bun with-env bun run src/index.ts",
13+ "start:prod": "bun with-env:prod bun run src/index.ts",
14+ "typecheck": "tsgo --noEmit --incremental false -p tsconfig.json",
15+ "clean": "bun -e \"import { rmSync } from 'node:fs'; for (const p of ['dist','node_modules','.turbo','coverage','.tsbuildinfo']) rmSync(p,{recursive:true,force:true});\""
16+ },
17+ "dependencies": {
18+ "@openassistant/core": "workspace:*",
19+ "effect": "latest"
20+ }
21+}
apps/gateway/src/index.ts+10−0
@@ -0,0 +1,10 @@
1+import { healthcheck } from "@openassistant/core";
2+import { Effect } from "effect";
3+
4+const program = Effect.flatMap(healthcheck, (status) =>
5+ Effect.sync(() => {
6+ console.log(`[gateway] healthcheck=${status}`);
7+ }),
8+);
9+
10+await Effect.runPromise(program);
apps/gateway/tsconfig.json+9−0
@@ -0,0 +1,9 @@
1+{
2+ "extends": "../../tsconfig.base.json",
3+ "compilerOptions": {
4+ "rootDir": "src"
5+ },
6+ "include": [
7+ "src/**/*.ts"
8+ ]
9+}
bun.lock+384−0
@@ -0,0 +1,384 @@
1+{
2+ "lockfileVersion": 1,
3+ "configVersion": 1,
4+ "workspaces": {
5+ "": {
6+ "name": "assistant",
7+ "devDependencies": {
8+ "@effect/language-service": "latest",
9+ "@effect/vitest": "^0.27.0",
10+ "@types/bun": "latest",
11+ "@typescript/native-preview": "7.0.0-dev.20260205.1",
12+ "typescript": "latest",
13+ "vitest": "^3.2.0",
14+ },
15+ },
16+ "apps/discord-bot": {
17+ "name": "@openassistant/discord-bot",
18+ "dependencies": {
19+ "@openassistant/core": "workspace:*",
20+ "ai": "^6.0.73",
21+ "ai-sdk-provider-claude-code": "^3.3.6",
22+ "discord.js": "^14.17.3",
23+ "effect": "latest",
24+ "zod": "^4.3.6",
25+ },
26+ },
27+ "apps/gateway": {
28+ "name": "@openassistant/gateway",
29+ "dependencies": {
30+ "@openassistant/core": "workspace:*",
31+ "effect": "latest",
32+ },
33+ },
34+ "packages/core": {
35+ "name": "@openassistant/core",
36+ "dependencies": {
37+ "effect": "latest",
38+ },
39+ },
40+ },
41+ "packages": {
42+ "@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.36", "", { "dependencies": { "@ai-sdk/provider": "3.0.7", "@ai-sdk/provider-utils": "4.0.13", "@vercel/oidc": "3.1.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-2r1Q6azvqMYxQ1hqfWZmWg4+8MajoldD/ty65XdhCaCoBfvDu7trcvxXDfTSU+3/wZ1JIDky46SWYFOHnTbsBw=="],
43+
44+ "@ai-sdk/provider": ["@ai-sdk/provider@3.0.7", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-VkPLrutM6VdA924/mG8OS+5frbVTcu6e046D2bgDo00tehBANR1QBJ/mPcZ9tXMFOsVcm6SQArOregxePzTFPw=="],
45+
46+ "@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.13", "", { "dependencies": { "@ai-sdk/provider": "3.0.7", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-HHG72BN4d+OWTcq2NwTxOm/2qvk1duYsnhCDtsbYwn/h/4zeqURu1S0+Cn0nY2Ysq9a9HGKvrYuMn9bgFhR2Og=="],
47+
48+ "@anthropic-ai/claude-agent-sdk": ["@anthropic-ai/claude-agent-sdk@0.2.32", "", { "optionalDependencies": { "@img/sharp-darwin-arm64": "^0.33.5", "@img/sharp-darwin-x64": "^0.33.5", "@img/sharp-linux-arm": "^0.33.5", "@img/sharp-linux-arm64": "^0.33.5", "@img/sharp-linux-x64": "^0.33.5", "@img/sharp-linuxmusl-arm64": "^0.33.5", "@img/sharp-linuxmusl-x64": "^0.33.5", "@img/sharp-win32-x64": "^0.33.5" }, "peerDependencies": { "zod": "^4.0.0" } }, "sha512-8AtsSx/M9jxd0ihS08eqa7VireTEuwQy0i1+6ZJX93LECT6Svlf47dPJiAm7JB+BhVMmwTfQeS6x1akIcCfvbQ=="],
49+
50+ "@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=="],
51+
52+ "@discordjs/collection": ["@discordjs/collection@1.5.3", "", {}, "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ=="],
53+
54+ "@discordjs/formatters": ["@discordjs/formatters@0.6.2", "", { "dependencies": { "discord-api-types": "^0.38.33" } }, "sha512-y4UPwWhH6vChKRkGdMB4odasUbHOUwy7KL+OVwF86PvT6QVOwElx+TiI1/6kcmcEe+g5YRXJFiXSXUdabqZOvQ=="],
55+
56+ "@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=="],
57+
58+ "@discordjs/util": ["@discordjs/util@1.2.0", "", { "dependencies": { "discord-api-types": "^0.38.33" } }, "sha512-3LKP7F2+atl9vJFhaBjn4nOaSWahZ/yWjOvA4e5pnXkt2qyXRCHLxoBQy81GFtLGCq7K9lPm9R517M1U+/90Qg=="],
59+
60+ "@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=="],
61+
62+ "@effect/language-service": ["@effect/language-service@0.73.0", "", { "bin": { "effect-language-service": "cli.js" } }, "sha512-/SGoq50VDm/XwQI6d0cReYcLjwfdqwZv7uEJp92+ssx5vLCsm+QvHf4Ul6l6PYVzorsgAp/b6fhAwI2VSkqcJQ=="],
63+
64+ "@effect/vitest": ["@effect/vitest@0.27.0", "", { "peerDependencies": { "effect": "^3.19.0", "vitest": "^3.2.0" } }, "sha512-8bM7n9xlMUYw9GqPIVgXFwFm2jf27m/R7psI64PGpwU5+26iwyxp9eAXEsfT5S6lqztYfpQQ1Ubp5o6HfNYzJQ=="],
65+
66+ "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.3", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg=="],
67+
68+ "@esbuild/android-arm": ["@esbuild/android-arm@0.27.3", "", { "os": "android", "cpu": "arm" }, "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA=="],
69+
70+ "@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.3", "", { "os": "android", "cpu": "arm64" }, "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg=="],
71+
72+ "@esbuild/android-x64": ["@esbuild/android-x64@0.27.3", "", { "os": "android", "cpu": "x64" }, "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ=="],
73+
74+ "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg=="],
75+
76+ "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg=="],
77+
78+ "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.3", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w=="],
79+
80+ "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA=="],
81+
82+ "@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.3", "", { "os": "linux", "cpu": "arm" }, "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw=="],
83+
84+ "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg=="],
85+
86+ "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.3", "", { "os": "linux", "cpu": "ia32" }, "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg=="],
87+
88+ "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA=="],
89+
90+ "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw=="],
91+
92+ "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA=="],
93+
94+ "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ=="],
95+
96+ "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw=="],
97+
98+ "@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.3", "", { "os": "linux", "cpu": "x64" }, "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA=="],
99+
100+ "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA=="],
101+
102+ "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.3", "", { "os": "none", "cpu": "x64" }, "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA=="],
103+
104+ "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.3", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw=="],
105+
106+ "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.3", "", { "os": "openbsd", "cpu": "x64" }, "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ=="],
107+
108+ "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g=="],
109+
110+ "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.3", "", { "os": "sunos", "cpu": "x64" }, "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA=="],
111+
112+ "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA=="],
113+
114+ "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q=="],
115+
116+ "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.3", "", { "os": "win32", "cpu": "x64" }, "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA=="],
117+
118+ "@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.0.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ=="],
119+
120+ "@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.0.4" }, "os": "darwin", "cpu": "x64" }, "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q=="],
121+
122+ "@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.0.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg=="],
123+
124+ "@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.0.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ=="],
125+
126+ "@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.0.5", "", { "os": "linux", "cpu": "arm" }, "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g=="],
127+
128+ "@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA=="],
129+
130+ "@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw=="],
131+
132+ "@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA=="],
133+
134+ "@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw=="],
135+
136+ "@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.0.5" }, "os": "linux", "cpu": "arm" }, "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ=="],
137+
138+ "@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.0.4" }, "os": "linux", "cpu": "arm64" }, "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA=="],
139+
140+ "@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.0.4" }, "os": "linux", "cpu": "x64" }, "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA=="],
141+
142+ "@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" }, "os": "linux", "cpu": "arm64" }, "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g=="],
143+
144+ "@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.0.4" }, "os": "linux", "cpu": "x64" }, "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw=="],
145+
146+ "@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.33.5", "", { "os": "win32", "cpu": "x64" }, "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg=="],
147+
148+ "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
149+
150+ "@openassistant/core": ["@openassistant/core@workspace:packages/core"],
151+
152+ "@openassistant/discord-bot": ["@openassistant/discord-bot@workspace:apps/discord-bot"],
153+
154+ "@openassistant/gateway": ["@openassistant/gateway@workspace:apps/gateway"],
155+
156+ "@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
157+
158+ "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.57.1", "", { "os": "android", "cpu": "arm" }, "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg=="],
159+
160+ "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.57.1", "", { "os": "android", "cpu": "arm64" }, "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w=="],
161+
162+ "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.57.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg=="],
163+
164+ "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.57.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w=="],
165+
166+ "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.57.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug=="],
167+
168+ "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.57.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q=="],
169+
170+ "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.57.1", "", { "os": "linux", "cpu": "arm" }, "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw=="],
171+
172+ "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.57.1", "", { "os": "linux", "cpu": "arm" }, "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw=="],
173+
174+ "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.57.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g=="],
175+
176+ "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.57.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q=="],
177+
178+ "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA=="],
179+
180+ "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw=="],
181+
182+ "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.57.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w=="],
183+
184+ "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.57.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw=="],
185+
186+ "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A=="],
187+
188+ "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw=="],
189+
190+ "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.57.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg=="],
191+
192+ "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.57.1", "", { "os": "linux", "cpu": "x64" }, "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg=="],
193+
194+ "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.57.1", "", { "os": "linux", "cpu": "x64" }, "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw=="],
195+
196+ "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.57.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw=="],
197+
198+ "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.57.1", "", { "os": "none", "cpu": "arm64" }, "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ=="],
199+
200+ "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.57.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ=="],
201+
202+ "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.57.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew=="],
203+
204+ "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.57.1", "", { "os": "win32", "cpu": "x64" }, "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ=="],
205+
206+ "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.57.1", "", { "os": "win32", "cpu": "x64" }, "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA=="],
207+
208+ "@sapphire/async-queue": ["@sapphire/async-queue@1.5.5", "", {}, "sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg=="],
209+
210+ "@sapphire/shapeshift": ["@sapphire/shapeshift@4.0.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "lodash": "^4.17.21" } }, "sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg=="],
211+
212+ "@sapphire/snowflake": ["@sapphire/snowflake@3.5.3", "", {}, "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ=="],
213+
214+ "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
215+
216+ "@types/bun": ["@types/bun@1.3.8", "", { "dependencies": { "bun-types": "1.3.8" } }, "sha512-3LvWJ2q5GerAXYxO2mffLTqOzEu5qnhEAlh48Vnu8WQfnmSwbgagjGZV6BoHKJztENYEDn6QmVd949W4uESRJA=="],
217+
218+ "@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="],
219+
220+ "@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="],
221+
222+ "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
223+
224+ "@types/node": ["@types/node@25.2.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-CPrnr8voK8vC6eEtyRzvMpgp3VyVRhgclonE7qYi6P9sXwYb59ucfrnmFBTaP0yUi8Gk4yZg/LlTJULGxvTNsg=="],
225+
226+ "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="],
227+
228+ "@typescript/native-preview": ["@typescript/native-preview@7.0.0-dev.20260205.1", "", { "optionalDependencies": { "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20260205.1", "@typescript/native-preview-darwin-x64": "7.0.0-dev.20260205.1", "@typescript/native-preview-linux-arm": "7.0.0-dev.20260205.1", "@typescript/native-preview-linux-arm64": "7.0.0-dev.20260205.1", "@typescript/native-preview-linux-x64": "7.0.0-dev.20260205.1", "@typescript/native-preview-win32-arm64": "7.0.0-dev.20260205.1", "@typescript/native-preview-win32-x64": "7.0.0-dev.20260205.1" }, "bin": { "tsgo": "bin/tsgo.js" } }, "sha512-eSgzYCbdCXP/E0XL53yIMZNLoY3z1xMOgGyjstVLgUCMLv1yNrFvkhKhHFjM84OTY/LxqRb6ACtvjFO/oSZzvQ=="],
229+
230+ "@typescript/native-preview-darwin-arm64": ["@typescript/native-preview-darwin-arm64@7.0.0-dev.20260205.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ULATKP9a26qh8vcmP4qPz8UugGKIwhQPKi3NhvlbTPwhl3fMd3GJd9/B9LJSHw7lIuELQGZxhSlDq9l0FMb/FQ=="],
231+
232+ "@typescript/native-preview-darwin-x64": ["@typescript/native-preview-darwin-x64@7.0.0-dev.20260205.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-moaKDZHK2dbgcHCnxcwhH8kYRgY69wzPcH5hCNaSrmpbC+Garr78oLtyXot2EDotRDT9foeYsWKdmD6Hx/ypxg=="],
233+
234+ "@typescript/native-preview-linux-arm": ["@typescript/native-preview-linux-arm@7.0.0-dev.20260205.1", "", { "os": "linux", "cpu": "arm" }, "sha512-3qfjUQlYCkwQmbpIeXMw75bLXkCI3Uo88Ug1n9p4j6KFaek5TjnHOTmlO6V3pkyH9pEXQEVXTn0pXzQytxqEqw=="],
235+
236+ "@typescript/native-preview-linux-arm64": ["@typescript/native-preview-linux-arm64@7.0.0-dev.20260205.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-Wfp2bPmrTLb+dpp2bHDjMqMKGjQ9dp5KSw0jV4LSlbgcVvRSEWqs2ByVVj61Z4qiHgwlVyoPTewdan2CWnoBgQ=="],
237+
238+ "@typescript/native-preview-linux-x64": ["@typescript/native-preview-linux-x64@7.0.0-dev.20260205.1", "", { "os": "linux", "cpu": "x64" }, "sha512-p59oY35gvvmdy/iZYxdbFAUXusb7joX2i1Nwl15i4TOn52NcIcW3wb9U/uBrIXKev5VEdlH6BS6VA6dM57zD6w=="],
239+
240+ "@typescript/native-preview-win32-arm64": ["@typescript/native-preview-win32-arm64@7.0.0-dev.20260205.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-+NQTlmvtZEXwIlw8j+tvAAn1gLDqyWJEjnA5vmT9MoJuEBrxvuS8azn/q26MOp/w8bWfxe3haVyB+L4VurCF6w=="],
241+
242+ "@typescript/native-preview-win32-x64": ["@typescript/native-preview-win32-x64@7.0.0-dev.20260205.1", "", { "os": "win32", "cpu": "x64" }, "sha512-kRa4kaiORAWQx9sHylewUhKsNxz3dRBy6AM/U02UebJRlt6c+JnSjIxAFP+iNQaRpoYNs8UdKKGPrHc7Q0oYow=="],
243+
244+ "@vercel/oidc": ["@vercel/oidc@3.1.0", "", {}, "sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w=="],
245+
246+ "@vitest/expect": ["@vitest/expect@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig=="],
247+
248+ "@vitest/mocker": ["@vitest/mocker@3.2.4", "", { "dependencies": { "@vitest/spy": "3.2.4", "estree-walker": "^3.0.3", "magic-string": "^0.30.17" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "optionalPeers": ["msw", "vite"] }, "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ=="],
249+
250+ "@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="],
251+
252+ "@vitest/runner": ["@vitest/runner@3.2.4", "", { "dependencies": { "@vitest/utils": "3.2.4", "pathe": "^2.0.3", "strip-literal": "^3.0.0" } }, "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ=="],
253+
254+ "@vitest/snapshot": ["@vitest/snapshot@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "magic-string": "^0.30.17", "pathe": "^2.0.3" } }, "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ=="],
255+
256+ "@vitest/spy": ["@vitest/spy@3.2.4", "", { "dependencies": { "tinyspy": "^4.0.3" } }, "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw=="],
257+
258+ "@vitest/utils": ["@vitest/utils@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="],
259+
260+ "@vladfrangu/async_event_emitter": ["@vladfrangu/async_event_emitter@2.4.7", "", {}, "sha512-Xfe6rpCTxSxfbswi/W/Pz7zp1WWSNn4A0eW4mLkQUewCrXXtMj31lCg+iQyTkh/CkusZSq9eDflu7tjEDXUY6g=="],
261+
262+ "ai": ["ai@6.0.73", "", { "dependencies": { "@ai-sdk/gateway": "3.0.36", "@ai-sdk/provider": "3.0.7", "@ai-sdk/provider-utils": "4.0.13", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-p2/ICXIjAM4+bIFHEkAB+l58zq+aTmxAkotsb6doNt/CEms72zt6gxv2ky1fQDwU4ecMOcmMh78VJUSEKECzlg=="],
263+
264+ "ai-sdk-provider-claude-code": ["ai-sdk-provider-claude-code@3.3.6", "", { "dependencies": { "@ai-sdk/provider": "^3.0.0", "@ai-sdk/provider-utils": "^4.0.1", "@anthropic-ai/claude-agent-sdk": "^0.2.23" }, "peerDependencies": { "zod": "^4.0.0" } }, "sha512-lDJJrQqe2A6tAH96ZLsZW39blkBh3jF1A4rs8B6CJyjNhLNeq46KmxkKDPYDoZw4xbILvnAknjXQNhMSZLnEYg=="],
265+
266+ "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="],
267+
268+ "bun-types": ["bun-types@1.3.8", "", { "dependencies": { "@types/node": "*" } }, "sha512-fL99nxdOWvV4LqjmC+8Q9kW3M4QTtTR1eePs94v5ctGqU8OeceWrSUaRw3JYb7tU3FkMIAjkueehrHPPPGKi5Q=="],
269+
270+ "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="],
271+
272+ "chai": ["chai@5.3.3", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="],
273+
274+ "check-error": ["check-error@2.1.3", "", {}, "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA=="],
275+
276+ "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
277+
278+ "deep-eql": ["deep-eql@5.0.2", "", {}, "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q=="],
279+
280+ "discord-api-types": ["discord-api-types@0.38.38", "", {}, "sha512-7qcM5IeZrfb+LXW07HvoI5L+j4PQeMZXEkSm1htHAHh4Y9JSMXBWjy/r7zmUCOj4F7zNjMcm7IMWr131MT2h0Q=="],
281+
282+ "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=="],
283+
284+ "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=="],
285+
286+ "es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="],
287+
288+ "esbuild": ["esbuild@0.27.3", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.3", "@esbuild/android-arm": "0.27.3", "@esbuild/android-arm64": "0.27.3", "@esbuild/android-x64": "0.27.3", "@esbuild/darwin-arm64": "0.27.3", "@esbuild/darwin-x64": "0.27.3", "@esbuild/freebsd-arm64": "0.27.3", "@esbuild/freebsd-x64": "0.27.3", "@esbuild/linux-arm": "0.27.3", "@esbuild/linux-arm64": "0.27.3", "@esbuild/linux-ia32": "0.27.3", "@esbuild/linux-loong64": "0.27.3", "@esbuild/linux-mips64el": "0.27.3", "@esbuild/linux-ppc64": "0.27.3", "@esbuild/linux-riscv64": "0.27.3", "@esbuild/linux-s390x": "0.27.3", "@esbuild/linux-x64": "0.27.3", "@esbuild/netbsd-arm64": "0.27.3", "@esbuild/netbsd-x64": "0.27.3", "@esbuild/openbsd-arm64": "0.27.3", "@esbuild/openbsd-x64": "0.27.3", "@esbuild/openharmony-arm64": "0.27.3", "@esbuild/sunos-x64": "0.27.3", "@esbuild/win32-arm64": "0.27.3", "@esbuild/win32-ia32": "0.27.3", "@esbuild/win32-x64": "0.27.3" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg=="],
289+
290+ "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="],
291+
292+ "eventsource-parser": ["eventsource-parser@3.0.6", "", {}, "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg=="],
293+
294+ "expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="],
295+
296+ "fast-check": ["fast-check@3.23.2", "", { "dependencies": { "pure-rand": "^6.1.0" } }, "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A=="],
297+
298+ "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
299+
300+ "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
301+
302+ "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
303+
304+ "js-tokens": ["js-tokens@9.0.1", "", {}, "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ=="],
305+
306+ "json-schema": ["json-schema@0.4.0", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="],
307+
308+ "lodash": ["lodash@4.17.23", "", {}, "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w=="],
309+
310+ "lodash.snakecase": ["lodash.snakecase@4.1.1", "", {}, "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw=="],
311+
312+ "loupe": ["loupe@3.2.1", "", {}, "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ=="],
313+
314+ "magic-bytes.js": ["magic-bytes.js@1.13.0", "", {}, "sha512-afO2mnxW7GDTXMm5/AoN1WuOcdoKhtgXjIvHmobqTD1grNplhGdv3PFOyjCVmrnOZBIT/gD/koDKpYG+0mvHcg=="],
315+
316+ "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
317+
318+ "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
319+
320+ "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
321+
322+ "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
323+
324+ "pathval": ["pathval@2.0.1", "", {}, "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ=="],
325+
326+ "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
327+
328+ "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
329+
330+ "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
331+
332+ "pure-rand": ["pure-rand@6.1.0", "", {}, "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA=="],
333+
334+ "rollup": ["rollup@4.57.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.57.1", "@rollup/rollup-android-arm64": "4.57.1", "@rollup/rollup-darwin-arm64": "4.57.1", "@rollup/rollup-darwin-x64": "4.57.1", "@rollup/rollup-freebsd-arm64": "4.57.1", "@rollup/rollup-freebsd-x64": "4.57.1", "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", "@rollup/rollup-linux-arm-musleabihf": "4.57.1", "@rollup/rollup-linux-arm64-gnu": "4.57.1", "@rollup/rollup-linux-arm64-musl": "4.57.1", "@rollup/rollup-linux-loong64-gnu": "4.57.1", "@rollup/rollup-linux-loong64-musl": "4.57.1", "@rollup/rollup-linux-ppc64-gnu": "4.57.1", "@rollup/rollup-linux-ppc64-musl": "4.57.1", "@rollup/rollup-linux-riscv64-gnu": "4.57.1", "@rollup/rollup-linux-riscv64-musl": "4.57.1", "@rollup/rollup-linux-s390x-gnu": "4.57.1", "@rollup/rollup-linux-x64-gnu": "4.57.1", "@rollup/rollup-linux-x64-musl": "4.57.1", "@rollup/rollup-openbsd-x64": "4.57.1", "@rollup/rollup-openharmony-arm64": "4.57.1", "@rollup/rollup-win32-arm64-msvc": "4.57.1", "@rollup/rollup-win32-ia32-msvc": "4.57.1", "@rollup/rollup-win32-x64-gnu": "4.57.1", "@rollup/rollup-win32-x64-msvc": "4.57.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A=="],
335+
336+ "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="],
337+
338+ "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
339+
340+ "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="],
341+
342+ "std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="],
343+
344+ "strip-literal": ["strip-literal@3.1.0", "", { "dependencies": { "js-tokens": "^9.0.1" } }, "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg=="],
345+
346+ "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="],
347+
348+ "tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="],
349+
350+ "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
351+
352+ "tinypool": ["tinypool@1.1.1", "", {}, "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg=="],
353+
354+ "tinyrainbow": ["tinyrainbow@2.0.0", "", {}, "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw=="],
355+
356+ "tinyspy": ["tinyspy@4.0.4", "", {}, "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q=="],
357+
358+ "ts-mixer": ["ts-mixer@6.0.4", "", {}, "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA=="],
359+
360+ "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
361+
362+ "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
363+
364+ "undici": ["undici@6.21.3", "", {}, "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw=="],
365+
366+ "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
367+
368+ "vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="],
369+
370+ "vite-node": ["vite-node@3.2.4", "", { "dependencies": { "cac": "^6.7.14", "debug": "^4.4.1", "es-module-lexer": "^1.7.0", "pathe": "^2.0.3", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "bin": { "vite-node": "vite-node.mjs" } }, "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg=="],
371+
372+ "vitest": ["vitest@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", "@vitest/mocker": "3.2.4", "@vitest/pretty-format": "^3.2.4", "@vitest/runner": "3.2.4", "@vitest/snapshot": "3.2.4", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "debug": "^4.4.1", "expect-type": "^1.2.1", "magic-string": "^0.30.17", "pathe": "^2.0.3", "picomatch": "^4.0.2", "std-env": "^3.9.0", "tinybench": "^2.9.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.14", "tinypool": "^1.1.1", "tinyrainbow": "^2.0.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", "vite-node": "3.2.4", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "@vitest/browser": "3.2.4", "@vitest/ui": "3.2.4", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@types/debug", "@types/node", "@vitest/browser", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A=="],
373+
374+ "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="],
375+
376+ "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=="],
377+
378+ "zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
379+
380+ "@discordjs/rest/@discordjs/collection": ["@discordjs/collection@2.1.1", "", {}, "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg=="],
381+
382+ "@discordjs/ws/@discordjs/collection": ["@discordjs/collection@2.1.1", "", {}, "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg=="],
383+ }
384+}
index.ts+1−0
@@ -0,0 +1 @@
1+console.log("Hello via Bun!");
package.json+30−0
@@ -0,0 +1,30 @@
1+{
2+ "name": "openassistant",
3+ "private": true,
4+ "type": "module",
5+ "workspaces": [
6+ "apps/*",
7+ "packages/*"
8+ ],
9+ "scripts": {
10+ "with-env": "bun run --env-file=.env --",
11+ "with-env:prod": "bun run --env-file=.env.production --",
12+ "prepare": "effect-language-service patch",
13+ "typecheck": "bun run --filter '@openassistant/core' typecheck && bun run --filter '@openassistant/gateway' typecheck && bun run --filter '@openassistant/discord-bot' typecheck",
14+ "typecheck:tsc": "tsc --noEmit --incremental false -p packages/core/tsconfig.json && tsc --noEmit --incremental false -p apps/gateway/tsconfig.json && tsc --noEmit --incremental false -p apps/discord-bot/tsconfig.json",
15+ "build": "bun run typecheck",
16+ "test": "vitest run",
17+ "test:watch": "vitest",
18+ "clean": "bun run --filter '@openassistant/core' clean && bun run --filter '@openassistant/gateway' clean && bun run --filter '@openassistant/discord-bot' clean",
19+ "dev:gateway": "bun run --cwd apps/gateway dev",
20+ "dev:discord": "bun run --cwd apps/discord-bot dev"
21+ },
22+ "devDependencies": {
23+ "@effect/language-service": "latest",
24+ "@effect/vitest": "^0.27.0",
25+ "@types/bun": "latest",
26+ "@typescript/native-preview": "7.0.0-dev.20260205.1",
27+ "typescript": "latest",
28+ "vitest": "^3.2.0"
29+ }
30+}
packages/core/package.json+17−0
@@ -0,0 +1,17 @@
1+{
2+ "name": "@openassistant/core",
3+ "private": true,
4+ "type": "module",
5+ "exports": {
6+ ".": "./src/index.ts"
7+ },
8+ "scripts": {
9+ "with-env": "bun run --env-file=../../.env --",
10+ "with-env:prod": "bun run --env-file=../../.env.production --",
11+ "typecheck": "tsgo --noEmit --incremental false -p tsconfig.json",
12+ "clean": "bun -e \"import { rmSync } from 'node:fs'; for (const p of ['dist','node_modules','.turbo','coverage','.tsbuildinfo']) rmSync(p,{recursive:true,force:true});\""
13+ },
14+ "dependencies": {
15+ "effect": "latest"
16+ }
17+}
packages/core/src/codemode/runner.test.ts+117−0
@@ -0,0 +1,117 @@
1+import { expect, it } from "@effect/vitest";
2+import { Effect } from "effect";
3+import { describe } from "vitest";
4+import {
5+ createCodeModeRunner,
6+ defineTool,
7+} from "./runner.js";
8+
9+describe("codemode runner", () => {
10+ it.effect("runs read tools without approval", () =>
11+ Effect.gen(function* () {
12+ const approvalCalls: Array<{ toolPath: string }> = [];
13+ const runner = createCodeModeRunner({
14+ tools: {
15+ calendar: {
16+ read: defineTool({
17+ kind: "read",
18+ approval: "auto",
19+ run: (input: { id: string }) => Effect.succeed({ id: input.id }),
20+ }),
21+ },
22+ },
23+ requestApproval: (request) =>
24+ Effect.sync(() => {
25+ approvalCalls.push({ toolPath: request.toolPath });
26+ return "approved" as const;
27+ }),
28+ });
29+
30+ const result = yield* runner.run({
31+ code: "const event = await tools.calendar.read({ id: 'evt_1' }); return event.id;",
32+ });
33+
34+ expect(result.ok).toBe(true);
35+ if (result.ok) {
36+ expect(result.value).toBe("evt_1");
37+ }
38+ expect(approvalCalls).toHaveLength(0);
39+ expect(result.receipts).toHaveLength(1);
40+ expect(result.receipts[0]?.decision).toBe("auto");
41+ expect(result.receipts[0]?.status).toBe("succeeded");
42+ expect(result.receipts[0]?.toolPath).toBe("calendar.read");
43+ }),
44+ );
45+
46+ it.effect("blocks writes when denied and records receipt", () =>
47+ Effect.gen(function* () {
48+ let ranMutation = false;
49+ const runner = createCodeModeRunner({
50+ tools: {
51+ calendar: {
52+ update: defineTool({
53+ kind: "write",
54+ approval: "required",
55+ run: (_input: { id: string }) =>
56+ Effect.sync(() => {
57+ ranMutation = true;
58+ return { ok: true };
59+ }),
60+ }),
61+ },
62+ },
63+ requestApproval: () => Effect.succeed("denied" as const),
64+ });
65+
66+ const result = yield* runner.run({
67+ code: "await tools.calendar.update({ id: 'evt_2' }); return 'done';",
68+ });
69+
70+ expect(result.ok).toBe(false);
71+ if (!result.ok) {
72+ expect(result.error).toContain("denied");
73+ }
74+ expect(ranMutation).toBe(false);
75+ expect(result.receipts).toHaveLength(1);
76+ expect(result.receipts[0]?.decision).toBe("denied");
77+ expect(result.receipts[0]?.status).toBe("denied");
78+ expect(result.receipts[0]?.toolPath).toBe("calendar.update");
79+ }),
80+ );
81+
82+ it.effect("runs writes when approved and records success receipt", () =>
83+ Effect.gen(function* () {
84+ let ranMutation = false;
85+ const runner = createCodeModeRunner({
86+ tools: {
87+ calendar: {
88+ update: defineTool({
89+ kind: "write",
90+ approval: "required",
91+ run: (input: { id: string; title: string }) =>
92+ Effect.sync(() => {
93+ ranMutation = true;
94+ return { id: input.id, title: input.title };
95+ }),
96+ }),
97+ },
98+ },
99+ requestApproval: () => Effect.succeed("approved" as const),
100+ });
101+
102+ const result = yield* runner.run({
103+ code: "const out = await tools.calendar.update({ id: 'evt_3', title: 'Dinner' }); return out.title;",
104+ });
105+
106+ expect(result.ok).toBe(true);
107+ if (result.ok) {
108+ expect(result.value).toBe("Dinner");
109+ }
110+ expect(ranMutation).toBe(true);
111+ expect(result.receipts).toHaveLength(1);
112+ expect(result.receipts[0]?.decision).toBe("approved");
113+ expect(result.receipts[0]?.status).toBe("succeeded");
114+ expect(result.receipts[0]?.toolPath).toBe("calendar.update");
115+ }),
116+ );
117+});
packages/core/src/codemode/runner.ts+318−0
@@ -0,0 +1,318 @@
1+import { Effect } from "effect";
2+
3+export type ToolKind = "read" | "write";
4+export type ToolApprovalMode = "auto" | "required";
5+export type ApprovalDecision = "approved" | "denied";
6+export type ToolReceiptDecision = "auto" | ApprovalDecision;
7+export type ToolReceiptStatus = "succeeded" | "failed" | "denied";
8+
9+export interface ToolCallReceipt {
10+ callId: string;
11+ toolPath: string;
12+ kind: ToolKind;
13+ approval: ToolApprovalMode;
14+ decision: ToolReceiptDecision;
15+ status: ToolReceiptStatus;
16+ timestamp: string;
17+ inputPreview?: string;
18+ outputPreview?: string;
19+ error?: string;
20+}
21+
22+export interface ApprovalRequest {
23+ callId: string;
24+ toolPath: string;
25+ kind: ToolKind;
26+ approval: ToolApprovalMode;
27+ input: unknown;
28+ inputPreview?: string;
29+}
30+
31+export interface ToolDefinition<TInput = unknown, TOutput = unknown> {
32+ readonly _tag: "ToolDefinition";
33+ readonly kind: ToolKind;
34+ readonly approval: ToolApprovalMode;
35+ readonly run: (input: TInput) => Effect.Effect<TOutput, unknown>;
36+ readonly previewInput?: (input: TInput) => string | undefined;
37+ readonly previewOutput?: (output: TOutput) => string | undefined;
38+}
39+
40+export type ToolTree = {
41+ readonly [name: string]: ToolTree | ToolDefinition<any, any>;
42+};
43+
44+export interface CodeModeRunnerOptions {
45+ tools: ToolTree;
46+ requestApproval: (request: ApprovalRequest) => Effect.Effect<ApprovalDecision, unknown>;
47+ now?: () => Date;
48+ newCallId?: () => string;
49+}
50+
51+export interface RunCodeOptions {
52+ code: string;
53+}
54+
55+export type CodeModeRunResult =
56+ | {
57+ ok: true;
58+ value: unknown;
59+ receipts: ToolCallReceipt[];
60+ }
61+ | {
62+ ok: false;
63+ error: string;
64+ receipts: ToolCallReceipt[];
65+ };
66+
67+const MAX_PREVIEW_CHARS = 180;
68+const AsyncFunctionCtor = Object.getPrototypeOf(
69+ async (_tools: unknown) => undefined,
70+).constructor as new (argName: string, body: string) => (tools: unknown) => Promise<unknown>;
71+
72+export function defineTool<TInput, TOutput>(definition: {
73+ kind: ToolKind;
74+ approval: ToolApprovalMode;
75+ run: (input: TInput) => Effect.Effect<TOutput, unknown>;
76+ previewInput?: (input: TInput) => string | undefined;
77+ previewOutput?: (output: TOutput) => string | undefined;
78+}): ToolDefinition<TInput, TOutput> {
79+ return {
80+ _tag: "ToolDefinition",
81+ ...definition,
82+ };
83+}
84+
85+export function createCodeModeRunner(options: CodeModeRunnerOptions): {
86+ run: (params: RunCodeOptions) => Effect.Effect<CodeModeRunResult, never>;
87+} {
88+ const now = options.now ?? (() => new Date());
89+ const newCallId = options.newCallId ?? defaultCallId;
90+
91+ return {
92+ run: (params) => {
93+ const receipts: ToolCallReceipt[] = [];
94+ const tools = materializeToolTree({
95+ tools: options.tools,
96+ requestApproval: options.requestApproval,
97+ newCallId,
98+ now,
99+ receipts,
100+ });
101+
102+ return Effect.tryPromise({
103+ try: async () => {
104+ const execute = new AsyncFunctionCtor("tools", `"use strict";\n${params.code}`);
105+ const value = await execute(tools);
106+ return {
107+ ok: true as const,
108+ value,
109+ receipts: [...receipts],
110+ };
111+ },
112+ catch: (cause) => ({
113+ ok: false as const,
114+ error: describeUnknown(cause),
115+ receipts: [...receipts],
116+ }),
117+ }).pipe(
118+ Effect.catchAll((result) =>
119+ Effect.succeed({
120+ ok: false as const,
121+ error: result.error,
122+ receipts: result.receipts,
123+ }),
124+ ),
125+ );
126+ },
127+ };
128+}
129+
130+function materializeToolTree(params: {
131+ tools: ToolTree;
132+ requestApproval: (request: ApprovalRequest) => Effect.Effect<ApprovalDecision, unknown>;
133+ newCallId: () => string;
134+ now: () => Date;
135+ receipts: ToolCallReceipt[];
136+}): Record<string, unknown> {
137+ const out: Record<string, unknown> = {};
138+ for (const [name, node] of Object.entries(params.tools)) {
139+ const toolPath = name;
140+ out[name] = materializeToolNode({
141+ node,
142+ toolPath,
143+ requestApproval: params.requestApproval,
144+ newCallId: params.newCallId,
145+ now: params.now,
146+ receipts: params.receipts,
147+ });
148+ }
149+ return out;
150+}
151+
152+function materializeToolNode(params: {
153+ node: ToolTree | ToolDefinition<any, any>;
154+ toolPath: string;
155+ requestApproval: (request: ApprovalRequest) => Effect.Effect<ApprovalDecision, unknown>;
156+ newCallId: () => string;
157+ now: () => Date;
158+ receipts: ToolCallReceipt[];
159+}): unknown {
160+ if (isToolDefinition(params.node)) {
161+ return createToolInvoker({
162+ definition: params.node,
163+ toolPath: params.toolPath,
164+ requestApproval: params.requestApproval,
165+ newCallId: params.newCallId,
166+ now: params.now,
167+ receipts: params.receipts,
168+ });
169+ }
170+
171+ const out: Record<string, unknown> = {};
172+ for (const [name, child] of Object.entries(params.node)) {
173+ out[name] = materializeToolNode({
174+ node: child,
175+ toolPath: `${params.toolPath}.${name}`,
176+ requestApproval: params.requestApproval,
177+ newCallId: params.newCallId,
178+ now: params.now,
179+ receipts: params.receipts,
180+ });
181+ }
182+ return out;
183+}
184+
185+function createToolInvoker(params: {
186+ definition: ToolDefinition<any, any>;
187+ toolPath: string;
188+ requestApproval: (request: ApprovalRequest) => Effect.Effect<ApprovalDecision, unknown>;
189+ newCallId: () => string;
190+ now: () => Date;
191+ receipts: ToolCallReceipt[];
192+}): (input: unknown) => Promise<unknown> {
193+ return async (input: unknown) => {
194+ const callId = params.newCallId();
195+ const timestamp = params.now().toISOString();
196+ const inputPreview = previewValue(input, params.definition.previewInput);
197+
198+ let decision: ToolReceiptDecision = "auto";
199+ if (params.definition.approval === "required") {
200+ const approval = await Effect.runPromise(
201+ params.requestApproval({
202+ callId,
203+ toolPath: params.toolPath,
204+ kind: params.definition.kind,
205+ approval: params.definition.approval,
206+ input,
207+ ...(inputPreview !== undefined ? { inputPreview } : {}),
208+ }),
209+ );
210+ decision = approval;
211+ if (approval === "denied") {
212+ params.receipts.push({
213+ callId,
214+ toolPath: params.toolPath,
215+ kind: params.definition.kind,
216+ approval: params.definition.approval,
217+ decision,
218+ status: "denied",
219+ timestamp,
220+ ...(inputPreview !== undefined ? { inputPreview } : {}),
221+ });
222+ throw new Error(`Tool call denied: ${params.toolPath}`);
223+ }
224+ }
225+
226+ const execution = await Effect.runPromiseExit(params.definition.run(input));
227+ if (execution._tag === "Success") {
228+ const value = execution.value;
229+ const outputPreview = previewValue(value, params.definition.previewOutput);
230+ params.receipts.push({
231+ callId,
232+ toolPath: params.toolPath,
233+ kind: params.definition.kind,
234+ approval: params.definition.approval,
235+ decision,
236+ status: "succeeded",
237+ timestamp,
238+ ...(inputPreview !== undefined ? { inputPreview } : {}),
239+ ...(outputPreview !== undefined ? { outputPreview } : {}),
240+ });
241+ return value;
242+ }
243+
244+ const error = describeUnknown(execution.cause);
245+ params.receipts.push({
246+ callId,
247+ toolPath: params.toolPath,
248+ kind: params.definition.kind,
249+ approval: params.definition.approval,
250+ decision,
251+ status: "failed",
252+ timestamp,
253+ ...(inputPreview !== undefined ? { inputPreview } : {}),
254+ error,
255+ });
256+ throw new Error(error);
257+ };
258+}
259+
260+function isToolDefinition(value: unknown): value is ToolDefinition<any, any> {
261+ return (
262+ typeof value === "object" &&
263+ value !== null &&
264+ "_tag" in value &&
265+ (value as { _tag?: unknown })._tag === "ToolDefinition"
266+ );
267+}
268+
269+function previewValue<T>(
270+ value: T,
271+ customPreview: ((value: T) => string | undefined) | undefined,
272+): string | undefined {
273+ if (customPreview) {
274+ return truncatePreview(customPreview(value));
275+ }
276+ if (value === undefined) {
277+ return undefined;
278+ }
279+ return truncatePreview(safeStringify(value));
280+}
281+
282+function truncatePreview(value: string | undefined): string | undefined {
283+ if (!value) {
284+ return undefined;
285+ }
286+ if (value.length <= MAX_PREVIEW_CHARS) {
287+ return value;
288+ }
289+ return `${value.slice(0, MAX_PREVIEW_CHARS)}...`;
290+}
291+
292+function safeStringify(value: unknown): string {
293+ if (typeof value === "string") {
294+ return value;
295+ }
296+ try {
297+ return JSON.stringify(value);
298+ } catch {
299+ return String(value);
300+ }
301+}
302+
303+function defaultCallId(): string {
304+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
305+ return crypto.randomUUID();
306+ }
307+ return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
308+}
309+
310+function describeUnknown(cause: unknown): string {
311+ if (cause instanceof Error) {
312+ return cause.message;
313+ }
314+ if (typeof cause === "string") {
315+ return cause;
316+ }
317+ return safeStringify(cause);
318+}
packages/core/src/index.ts+4−0
@@ -0,0 +1,4 @@
1+import { Effect } from "effect";
2+
3+export const healthcheck = Effect.succeed("ok");
4+export * from "./codemode/runner.js";
packages/core/tsconfig.json+9−0
@@ -0,0 +1,9 @@
1+{
2+ "extends": "../../tsconfig.base.json",
3+ "compilerOptions": {
4+ "rootDir": "src"
5+ },
6+ "include": [
7+ "src/**/*.ts"
8+ ]
9+}
tsconfig.base.json+22−0
@@ -0,0 +1,22 @@
1+{
2+ "compilerOptions": {
3+ "target": "ES2022",
4+ "module": "NodeNext",
5+ "moduleResolution": "NodeNext",
6+ "moduleDetection": "force",
7+ "verbatimModuleSyntax": true,
8+ "rewriteRelativeImportExtensions": true,
9+ "strict": true,
10+ "exactOptionalPropertyTypes": true,
11+ "noUncheckedIndexedAccess": true,
12+ "noImplicitOverride": true,
13+ "noUnusedLocals": true,
14+ "noUnusedParameters": true,
15+ "skipLibCheck": true,
16+ "plugins": [
17+ {
18+ "name": "@effect/language-service"
19+ }
20+ ]
21+ }
22+}
tsconfig.json+14−0
@@ -0,0 +1,14 @@
1+{
2+ "files": [],
3+ "references": [
4+ {
5+ "path": "./packages/core"
6+ },
7+ {
8+ "path": "./apps/gateway"
9+ },
10+ {
11+ "path": "./apps/discord-bot"
12+ }
13+ ]
14+}
vitest.config.ts+8−0
@@ -0,0 +1,8 @@
1+import { defineConfig } from "vitest/config";
2+
3+export default defineConfig({
4+ test: {
5+ include: ["packages/**/*.test.ts", "apps/**/*.test.ts"],
6+ exclude: ["**/node_modules/**", ".context/**", ".reference/**"],
7+ },
8+});