Adds per-agent model configuration: ALLOWED_MODELS allowlist in
src/config/models.ts, model field in AgentYamlSchema (Zod default:
claude-sonnet-4-6), --model flag forwarded to the Claude CLI in
runner.ts, and a StringSelectMenuBuilder step in the /new-agent
command flow. Three unit tests cover model passthrough, default
fallback, and invalid-model rejection.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add deleteWorkspaceByChannelId() to DisclawDatabase
- Add src/lifecycle/categories.ts: ensureCategories() + moveToCategory()
- Add src/lifecycle/reconcile.ts: reconcile() assigns channels to active/inactive/archive categories on startup
- Add src/lifecycle/channel-delete-handler.ts: handles channelDelete events for management, category, and agent channels
- Wire lifecycle into bot.ts: state object, ensureCategories, reconcile, channelDelete listener
- Extend handleNewAgent() with optional activeCategoryId to place new channels in the active category
- Add 13 unit tests covering all lifecycle scenarios
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
All DIS-001 through DIS-110 set to status:done with merge dates.
BOARD.md reorganized with Done sections per phase.
Phase 1.5 Refinement epic added as planning placeholder.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two root causes for the failing timeout test:
1. Test config used `workspace_root` instead of `workspaces_root`, causing
Zod validation to fail silently, so resolveTimeoutMs() fell back to the
default 120s — the mock's 200ms close event always beat it.
2. Schema enforced `.int()` on claude_timeout_seconds, rejecting the test's
0.05s fractional value. Removed the integer constraint since sub-second
timeouts are valid (and useful for testing).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add RunResult type in src/agent/types.ts (success|timeout|cli-error|parse-error).
Runner resolves instead of rejects for all error cases.
Router handles all four status variants explicitly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add ChannelQueue in src/runtime/channel-queue.ts.
Router enqueues each message so same-channel requests run serially.
Different channels remain parallel.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The flush() helper now enforces the Discord hard limit by hard-splitting
any chunk that exceeds 2000 chars (e.g. when "\n```" was appended after
the soft-limit check). Also reserves 4 chars of overhead in the
soft-limit check when inside a fenced code block.
Adds a regression test that verifies no chunk exceeds 2000 chars even
when the fence closure is appended at the limit boundary.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Resolve conflict in src/router.ts: keep both splitForDiscord (DIS-105)
and sanitizeForDiscord (DIS-102). Apply splitForDiscord to the already
sanitized response text.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The root cause was twofold:
1. Raw (unsanitized) agent responses were stored in the conversation
history DB. On subsequent calls, these paths were fed back to the
agent as context, causing it to reproduce full paths regardless of
Discord-side sanitization.
2. repoRoot (workspace_path) was not passed to sanitizeForDiscord,
so workspace-specific paths were only partially masked via the
home directory fallback.
Fix: sanitize the full response once before both sending to Discord
and storing in the DB. Pass workspace_path as repoRoot for precise
path replacement. Add Windows path tests.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
os.homedir() returns backslashes on Windows but Claude Code outputs
paths with forward slashes. Replace both C:\Users\x and C:/Users/x
variants so home and repoRoot are always redacted.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Re-exported types via 'export type { X } from' are not available
as local names in the same file. Add explicit 'import type' so
TypeScript resolves the types used in function signatures.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add sanitizeForDiscord() in src/runtime/sanitize.ts.
Router applies sanitizer to all channel.send() calls.
Prevents path/token leaks into Discord channels.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
TypeScript does not copy non-.ts files. Add a cpSync step to the
build and dev scripts so dist/db/schema.sql exists at runtime.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Move all CREATE TABLE/INDEX DDL from database.ts to schema.sql.
database.ts loads and executes schema.sql via db.exec().
Add integration test verifying idempotent schema init.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add DisclawConfigSchema and AgentYamlSchema with Zod validation.
loader.ts and identity.ts validate external data at parse time.
Clear error messages with field name and expected type.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add splitForDiscord() in src/discord/split.ts.
Fences are properly closed and reopened across chunk boundaries.
Router uses splitForDiscord instead of internal splitMessage().
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace all console.* calls in src/ with pino child-loggers.
Add rootLogger + childLogger(component) in src/runtime/logger.ts.
pino-pretty used as dev transport only.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add Semaphore class in src/runtime/concurrency.ts.
Runner acquires/releases slot around claude spawn.
Default cap: 4, configurable via disclaw.yaml max_concurrent_agents.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Manual probe script covering session_id field path, --resume flow,
stream-json event types, and usage block field names.
Must be executed and filled in before Phase 2 release.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add vitest.config.mts with node environment and v8 coverage.
Add test:watch and test:coverage scripts to package.json.
Complete .env.example with all config fields from loader.ts.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
--bare disables OAuth/keychain auth and requires ANTHROPIC_API_KEY,
breaking Claude Pro/Max subscriptions. CLAUDE.md isolation is now
achieved structurally: workspaces live under ~/.disclaw/workspaces/
(outside the repo) so walk-up never reaches DisClaw's CLAUDE.md.
Update integration test to assert --bare is absent and verify correct
args (-p, --output-format json). Fix sanitize-env tests to reflect
CI=true removal (was breaking OAuth headless-mode detection).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>