| 8 May 202610:10 pm |
koda-core |
2.100.1current |
Fix session auto-naming bugs (task #299). Bug 1 — every named owner session_history archive (56/56) was titled 'PWA Updates and Task Progress' regardless of actual content. Cause: generateSessionTitle() in lib/sessions.js invoked Claude haiku via invokeClaudeCode, which spawned the CLI from cwd=KODA_DIR and so loaded the project's CLAUDE.md / skills / MEMORY.md — the Koda-flavoured project context biased the model toward generic Koda-PWA-themed titles regardless of the conversation sample. Fix: (a) added optional systemPrompt param to invokeClaudeOneShot in lib/claude.js (passes --system-prompt); (b) generateSessionTitle now filters slash commands and short pings out of the sample, skips auto-naming when fewer than 3 meaningful messages remain (caller falls back to first-message truncation), routes through invokeClaudeOneShot with cwd='/tmp' so the project context can't load, and rejects known-bad hallucinated titles as a backstop. Bug 2 — every archived row showed started_at = 2026-03-10T10:42:54 (the date the sessions row was first created in the DB). Cause: archiveSession() copied session.created_at into session_history.started_at, but saveSession() reuses the row across resets, only PATCHing session_id + last_active — created_at never refreshes. Fix: archiveSession now derives started_at from the earliest message timestamp for the claude_session_id, falling back to session.last_active or now(). |
| 8 May 20269:48 pm |
koda-core |
2.100.0 |
Channel-aware system prompt (task #290): buildSystemPrompt() now takes a channel parameter and produces a channel-specific identity line — 'operating via Signal messaging' / 'operating via WhatsApp messaging' / 'operating via the Koda web console' / 'running as an autonomous /work task'. Previously hardcoded to Signal regardless of inbound channel, which caused Koda to confidently misidentify WhatsApp conversations as Signal. Wired through all 5 call sites: server.js (handleMention passes channel from envelope), lib/api/messages.js + lib/api/chat.js (web), lib/commands/modify.js + lib/commands/work.js (context.channel). |
| 8 May 20268:05 pm |
koda-core |
2.99.2 |
Fix WAHA auto-recovery — FAILED-state handler in server.js was calling whatsappStartSession() which silently no-op'd with 422 'already running' (because WAHA's RESTART_ALL_SESSIONS auto-spawns a session on stop), leaving the session FAILED indefinitely. Added whatsappRestartSession() chokepoint in lib/channels/whatsapp.js (calls /sessions/{name}/restart for an explicit stop→start cycle); FAILED webhook handler now uses it. Added 60s restart cooldown to prevent kicking restart on every webhook during outages, and a 30s post-restart status check that escalates 'needs re-pair (QR scan)' to the owner over Signal if WAHA is still FAILED — caught by a 10min cooldown to prevent alert spam during long outages. Today's incident exposed the bug: WAHA went FAILED at 07:06 UTC, auto-restart returned 422, marked 'success', session stayed FAILED for 2hrs until manual intervention. |
| 6 May 202612:05 pm |
network-diagram-skill |
0.2.1current |
SKILL.md roadmap update: Phase 3 (flow tracer) marked 'not built'. The IR JSON from Phase 1 already contains every routing/NAT/zone field needed to answer flow questions ad-hoc — no subcommand or codified path-walker required. Revisit only if ad-hoc answers prove insufficient. |
| 6 May 202611:50 am |
network-diagram-skill |
0.2.0 |
Phase 2 — sanitise subcommand. New 'netdiag sanitise <files...> [-o OUT | --in-place | --suffix SFX]' strips secrets from configs before they leave the network. Three format-specific redactors: OPNsense XML (catch-all tag patterns + explicit secret tags + LDAP DNs, all multi-line aware for cert/key blocks), Ubiquiti EdgeSwitch CLI (username/enable/line passwords, SNMP community + auth/priv keys, RADIUS/TACACS keys, RIP/OSPF key strings, dot1x), and Proxmox VE (qemu-server vncpassword/cipassword/cikeys/sshkeys, /etc/network/interfaces wpa-psk/wpa-passphrase, root@pam!name=UUID API tokens). Shared audit pass after every sanitise: long base64/hex blob detector + keyword sweep (passw/secret/psk/bindpw/api_key/token/community/shared_key) + XML secret-tag scanner. CLI exits non-zero if any residual secrets detected. Smoke-tested on 4 sandpit configs: 2 + 30 + 122 + 26 redactions, all audit-clean, build still produces identical topology (4 devices, 79 interfaces, 5 VLANs, 8 networks, 8 links). Rules ported from owner's two reference shell scripts. |
| 6 May 202611:30 am |
network-diagram-skill |
0.1.0 |
Initial release of the network-diagram skill (#70 in skills/index.json). Deterministic parser→IR→renderer pipeline (no AI in the diagram path). Parsers: OPNsense/pfSense config.xml (interfaces, VLANs, LAGGs, bridges, gateways, static routes, NAT, firewall rules, DHCP), Ubiquiti EdgeSwitch CLI (hostname, mgmt IP, vlan database, port PVID/tagged/untagged participation, LAG members), Proxmox /etc/network/interfaces + qemu-server VM configs. Cross-device link inference: explicit override > L3 shared subnet (0.9) > port-description hostname match (0.7) > VLAN trunk co-presence (0.4). Renderers: Mermaid (graph TB with role + zone subgraphs, classDef styling, fa: icons) and draw.io / diagrams.net mxGraph XML with Cisco shape stencils (mxgraph.cisco_safe.devices.firewall/switch/router, mxgraph.cisco.servers, cloud). CLI: netdiag build <files...> [-o OUT] [--override topology.yaml]. Smoke-tested on 4 sandpit configs (fcrsw01, fcrfw, prodfw, testfw) → 4 devices, 79 interfaces, 5 VLANs, 8 networks, 8 inferred links. Phase 2 (codified non-AI secrets scan + REDACTED) and Phase 3 (flow tracer: 'show me how my traffic gets to internet') deferred. |
| 6 May 20269:10 am |
koda-console |
1.39.1current |
Two regression fixes from 1.39.0. (1) Date input collapsed to a thin line because appearance:none stripped iOS Safari's native intrinsic height — added min-height:2.5rem and line-height:1.5 to input[type=date] so empty fields stay readable. (2) Pull-to-refresh fired inside the edit-task modal when the user dragged down to scroll — PullToRefresh._start and _move now bail when document.body has class 'modal-open' (already toggled by Alpine x-effect on every modal backdrop). Synced from apps/_shared/ to console/dashboard/det22/usermgmt. |
| 6 May 20268:45 am |
koda-console |
1.39.0 |
Three PWA bug fixes (#282, #287, #288). (1) #282: Chat rename toast no longer misfires on long sessions — startRenameSession now falls back to chatActiveSessionId when chatViewingSession.sessionId is missing (which can happen via SSE timing, sessionStorage roundtrip, or Vue reactivity quirks); only blocks if there are also no messages. (2) #287: Add Task and Edit Task forms now show only Title + Description by default; Priority, Visibility, Labels, and Due date collapse under a 'More' chevron. Tightened textarea padding for visually shorter editing area without dropping below 16px font (which would trigger iOS focus-zoom). Widened '+ custom label' input from w-24 to w-32 so the placeholder is no longer clipped to '+ custom labl'. Added -webkit-appearance: none on input[type=date] so iOS native picker chrome no longer paints over the right edge — rounded corners now apply uniformly. (3) #288: Task list is now hidden via x-show="!taskExpanded" while the create form is open, so its sticky header (sort dropdown + Select button) no longer peeks through below the Create Task button as 'two half buttons'. |
| 4 May 20269:30 pm |
koda-core |
2.99.1 |
Drop success-side audit log for POST /api/auth/token (lib/api/auth.js). CF Access already logs each SSO upstream, so the koda-core row was duplicate noise — and clients can stampede this endpoint after a restart (see koda-console 1.38.1 for the root-cause fix). Three plan-driven restarts earlier this hour produced ~12k success rows in 6 minutes from owner alone. The denied path remains audited so refused tokens stay traceable. Restart required for lib/api/auth.js. |
| 4 May 20269:30 pm |
koda-console |
1.38.1 |
Single-flight Auth.acquire() in apps/console/js/auth.js to kill the JWT-refresh thundering herd. When N concurrent API calls all hit 401 simultaneously (typical after a Koda Core restart), every one of them previously called Auth.refresh() independently and fired its own POST /api/auth/token — sustained ~70 req/sec bursts (6884 requests with 0ms gap) confirmed in audit_log. Wrapping acquire() with an in-flight promise collapses the cascade to one POST per refresh window. SW CACHE_VERSION 1.38.0 → 1.38.1 to push the new auth.js to clients. |
| 4 May 20267:15 pm |
koda-core |
2.99.0 |
WhatsApp identity-gate fixes + orchestrator hardening (six functional changes across five files). (1) lib/channels/whatsapp.js — normaliseMentions() now emits BOTH bare-digits and full @lid/@c.us forms for every WAHA mentionedId so the verifiedIdentities Set holds whichever form the orchestrator emits; new fetchGroupParticipants() pulls live group rosters from WAHA. (2) server.js webhook — wires normaliseMentions into envelope.dataMessage.mentions. (3) lib/sessions.js — new getGroupMembersForChannel(channel, groupId) returns a uniform {phone, lid?, display_name, isYou, isOwner} shape across Signal + WhatsApp; existing getGroupMembersFromSignal kept for back-compat. (4) server.js processMessage — replaces Signal-only roster fetch with channel-aware helper, so WhatsApp groups now get the anti-roster-resolution instruction block. (5) lib/instructions.js — _renderGroupMembersBlock rewritten: channel-agnostic wording ('Signal' / 'WhatsApp' inferred from member shape), LID column for WhatsApp members, strengthened anti-resolution rule explicitly forbidding name OR roster-identifier resolution into params.id for verified intents. (6) lib/intents.js — _ensureUserExists is LID-aware: detects 15+ digit identifiers (with/without @lid/@c.us suffix), looks up users via whatsapp_id (tries both bare + @lid forms), generates a fresh users.uuid + stores canonical @lid in users.whatsapp_id when auto-creating. New _resolveUserByIdentifier helper used by both grant + revoke paths. (7) server.js intent loop — gate-failure messages coalesced: rejections collected, grouped by (action, gate, error), sent as ONE consolidated ⚠️ per group ('8 × grant_capability rejected: <error>'); when ALL parsed intents in a turn are rejected, appends corrective line 'My previous message about that action was premature — nothing was actually executed.' to address the optimistic-narrative-then-gate-failure UX bug. Restart required for lib/sessions.js, lib/instructions.js, lib/intents.js, server.js. |
| 4 May 20267:55 am |
usermgmt |
1.9.0current |
Email-optional create_user. Migration 0004 adds phone + whatsapp_id columns + indexes on managed_users. POST /users handler now requires at-least-one of (email, phone, signal_uuid, whatsapp_id) instead of email-only — group-chat users can be created with just a phone or platform identifier. is_proxy decoupled from email-presence (now an explicit opt-in, no longer auto-set when email missing). Per-identifier uniqueness checks added. CF Access sync + welcome email gated on email presence directly. Manifest bumped to 1.2.0 with target_display_name + target_phone added to grant_capability/revoke_capability so auto-create-on-grant has human-readable identifiers. |
| 4 May 20267:55 am |
koda-core |
2.98.0 |
Auto-create users row on grant_capability when target isn't yet registered. _executeInternalCapabilityIntent in lib/intents.js now calls _ensureUserExists(uuid, displayName, phone) before writing to the permissions table — if the row doesn't exist, it inserts with role='viewer' (lowest floor) using target_display_name + target_phone from the @-mention metadata. Makes capability granting fully self-contained for group-chat users: 'give @Imo research and transcribe' in a chat now creates her users row + grants both caps in one batch, no separate create_user call needed. Restart was required for lib/intents.js. |
| 3 May 20268:30 pm |
koda-core |
2.97.0 |
Capability/permission/role cleanup + conversational cap-grant intents. Added new /caps and /whocan slash commands for capability introspection (lib/commands/caps.js, lib/commands/whocan.js — surfaced in /help). Added grant_capability and revoke_capability intents on the usermgmt manifest with internal_handler routing in lib/intents.js — they write directly to the Postgres permissions table via PostgREST and bypass the usermgmt CF Worker (Postgres is canonical until v3.0 Postgres→D1 sync lands). New params.context_inject manifest field auto-derives workspace_id from message context so the owner can say 'give @Name research' inside a group chat without specifying the workspace explicitly. Documented the capability/permission/role distinction in CLAUDE.md and docs/identity-action-security.md, including a workspaces-without-apps subsection. Restart was required for lib/intents.js. |
| 3 May 20264:05 pm |
koda-core |
2.96.2 |
Final approval-flow cleanup — drop AUTO_APPROVE_GROUPS from .env and .env.example. Was dead since v2.96.0 dropped state.AUTO_APPROVE_GROUPS in lib/state.js. No code path remains. No restart required (.env edits only). |
| 3 May 20263:55 pm |
koda-core |
2.96.1 |
Cleanup follow-up to v2.96.0 — drop the two dead env vars left behind by the approval-flow removal. Removed AUTO_APPROVE_DEFAULT and APPROVAL_TIMEOUT_MINUTES from lib/config.js (const declarations + module.exports), lib/api/config.js (SAFE_CONFIG_KEYS + UPDATABLE_KEYS allowlists), .env, and .env.example. No consumer remained after the v2.96.0 removal. Restart required (lib/config.js + lib/api/config.js). |
| 3 May 20263:25 pm |
koda-core |
2.96.0 |
Remove /approve, /deny, /autoapprove and all approval plumbing. The flow was dead code — Claude CLI is invoked with --dangerously-skip-permissions so the tool_use → onApprovalNeeded → /approve|/deny path never fired; audit_log confirms zero approval events ever recorded. Removed: lib/commands/approval.js, lib/commands/autoapprove.js, requestApproval() in server.js, POST /approve/:id endpoint, state.pendingApprovals, state.AUTO_APPROVE_GROUPS, state.AUTO_APPROVE_DISABLED, isAutoApprove/onApprovalNeeded params from invokeClaudeCode + handleClaudeEvent (lib/claude.js), pendingApprovals fields from /api/jobs and /api/status responses, /approve /deny /autoapprove from /help. Two restarts required (server.js + lib/* changes were spread across multiple commits). |
| 3 May 20262:35 pm |
koda-core |
2.95.2 |
/help now surfaces all registered slash commands. Added /listening (with /listen alias), /share, /pages, /research, /resume, /app, /workspace, /activate, /deactivate, /leave, /jobs, /btw, /wandi, and /deny — each gated by appropriate role/capability so non-owners only see what they can actually use. Hot-reloaded — no restart required. |
| 3 May 20262:30 pm |
koda-core |
2.95.1 |
Add /listen as an alias for /listening (group ambient context backlog command). Hot-reloaded — no restart required. |
| 3 May 20262:25 pm |
koda-core |
2.95.0 |
Two post-EIP bug fixes. (1) Scheduler path doubling — scripts/scheduled-jobs/weekly-memory-audit.js and daily-task-review.js used path.join(__dirname, '..') after being moved into scheduled-jobs/, resolving to scripts/ instead of the project root and producing the doubled '/scripts/scripts/signal-notify.sh' path that broke every scheduled run. Fixed by climbing two levels. Same root cause hit the .env path on both files — fixed in the same commit. (2) WhatsApp HMAC auth — /webhook/whatsapp was rejecting every WAHA delivery with HTTP 401 because the previous gate checked X-Api-Key (a header WAHA does not auto-forward on outbound webhooks). Replaced with HMAC-SHA512 verification matching WAHA's actual contract: signature in X-Webhook-Hmac, algorithm in X-Webhook-Hmac-Algorithm (read from header so any algorithm WAHA advertises is honoured), HMAC computed over the raw request body and constant-time compared via crypto.timingSafeEqual. Required mounting express.json with a verify callback as a path-prefix middleware on /webhook/whatsapp BEFORE the global json middleware (otherwise the global consumes the stream and rawBody stays undefined). Added new env var WAHA_HMAC_KEY (Koda side) and recreated the WAHA container with WHATSAPP_HOOK_HMAC_KEY (WAHA side); 32-byte random hex shared secret never crosses the wire. Graceful degradation: if WAHA_HMAC_KEY is unset, accept with a warning to avoid chicken-and-egg lockout during config rollout. End-to-end verified: valid sig → 200, tampered body → 401, missing header → 401, real WAHA session.status delivery processed cleanly. Restart required. |
| 3 May 20261:30 am |
koda-core |
2.94.1 |
Calendar bundle auto-subscribe fix (REM-04 follow-up). When the owner shares a Google Calendar with the service account, the share grants ACL access but does NOT add the calendar to the SA's calendarList. Verified against Google Calendar API docs (developers.google.com/calendar/api/v3/reference): no API path enumerates calendars-with-ACL-but-no-subscription for a SA — calendarList.list, calendars.get, acl.list, freebusy.query all require a known calendarId or pre-existing subscription. Fix: hybrid persistent-cap + ad-hoc-subscribe. (1) New env var KODA_CALENDAR_SUBSCRIBE_IDS — comma-list of calendar IDs the owner has shared. listCalendars() diffs that env var against the SA's current calendarList on every call and inserts any missing IDs (one shot per ID, audit-logged, failures swallowed so a stale entry never breaks the listing). (2) New bundle action subscribeCalendar({calendarId, requestor}) — for ad-hoc additions when the owner shares a new calendar. Inserts the ID, audit-logs the action, AND appends it to KODA_CALENDAR_SUBSCRIBE_IDS in .env (and process.env) so future restarts replay the subscription — no /config dance needed. Idempotent. (3) bundles/calendar.yaml updated: removed the wrong 'missing = not shared' guidance, added 'Calendar visibility — sharing vs subscribing' section explaining the API gap, registered subscribe_calendar action with worked Bash example, documented the env var. (4) lib/commands/config.js — KODA_CALENDAR_SUBSCRIBE_IDS added to CONFIGURABLE_KEYS. (5) tests/lib-modules/lib-calendar.test.js — 5 new auto-subscribe tests added (12 total, all pass): missing-ID auto-subscribe, no double-subscribe, idempotent ad-hoc, env persist, failed-insert-doesn't-break-list. Smoke confirmed end-to-end: 5 calendars now visible (Koda + klauswjones@gmail.com + Friendship dates + Wandiligong + Family). Restart required. |
| 3 May 202612:50 am |
koda-core |
2.94.0 |
EIP remediation bundle 2 — closes REM-05 + REM-06 + REM-07 + REM-09 + REM-11 + REM-12 + REM-13 + REM-14 (8 of remaining 9 queue rows). (REM-05 / F-P2-A) tests/security/intents-dispatch.test.js — 18 hermetic behavioural tests covering every refusal path of checkBundleGate, validateIntent gates A/B/C, executeBatch destructive hold + post-confirm dispatch. Red-green verified. (REM-06 / F-P2-B + F-P2-C audit.js) lib/audit.js registered as code-enforced canonical writer via new owns_table_writes field; eslint catches direct pgPost('audit_log',...) outside lib/audit.js. lib/logging.logAudit kept as thin shim (avoids cascading migration of ~30 callers); lib/sync.collectAuditLogs migrated to audit.logAction. Hermetic L4-fallback tests added. (REM-07 / F-P2-C remaining 6) registered keysvc-client, auth-headers, usermgmt-client, sync, file-share, integrations chokepoints with appropriate owns_urls/owns_config_constants/owns_table_writes; 22 documented_bypasses for legitimate setup-script and boilerplate references. (REM-09 / F-P2-E) backfilled verification boxes in 13 archived EIP instruction files (45 ticked with commit refs, 15 marked NOT VERIFIED at archive time); sub-queue status note added. (REM-11 / F-P3-B) removed broken $schema reference from chokepoints.json. (REM-12 / F-P3-C) confirmed already addressed pre-audit (server.js routes through channels.whatsapp.getGroupSubject). (REM-13 / F-P3-D) CLAUDE.md Pre/Post Change Notifications section now distinguishes Signal notifications (always) from pre-edit checkpoint commits (only for risky/multi-step/restart-causing/>60s changes); 26% noise commits no longer mandated. (REM-14 / strand-8 caveat) tests/lib-modules/lib-claude-shutdown.test.js — 2 fake-timer tests proving the 30s force-resolve fallback fires when SIGKILL is ignored, and the double-resolve guard works. Red-green verified. Restart required. |
| 3 May 202612:30 am |
koda-core |
2.93.0 |
EIP-REM-04 — wire calendar execution path (bundle-as-docs pattern). Calendar wiring is the proof of the EIP pattern; per owner Q&A 2026-05-02 it is system infrastructure, not a Tier-2 app — no /calendar slash command, no apps/calendar/manifest.koda.json. Claude invokes lib/calendar.js via Bash + node -e using the calling pattern documented in bundles/calendar.yaml. Changes: (lib/calendar.js) added _writeAllowList/_assertWriteAllowed enforcing KODA_CALENDAR_WRITE_ID + KODA_CALENDAR_WRITE_IDS allow-list on createEvent/updateEvent/deleteEvent (fail-closed when unset; error message names offending calendar AND current allow-list). Audit-log calls already wired pre-existing on every entry point. (bundles/calendar.yaml) doubled in length with bundle-as-docs guidance: explicit Bash invocation pattern (cd /Users/koda/koda && node -e "..."), allow-list documentation, two worked examples (create-event + list-events with full RFC3339 Melbourne timezone), confirmation rules for destructive actions, anti-patterns. (lib/commands/config.js) added KODA_CALENDAR_WRITE_ID and KODA_CALENDAR_WRITE_IDS to CONFIGURABLE_KEYS so owner can set them via /config set from Signal DM. (tests/lib-modules/lib-calendar.test.js) 7 cases covering empty allow-list refusal, single-id, comma-list, combined, error-message contents, whitespace tolerance. Live verification: GCP creds + Klaus's Koda calendar (`176f2dac...@group.calendar.google.com`) reachable from CLI; full Signal end-to-end smoke test is owner-driven (Klaus sets KODA_CALENDAR_WRITE_ID then says e.g. "what's on today"). Restart required. |
| 2 May 202611:55 pm |
koda-core |
2.92.0 |
EIP remediation bundle — closes REM-01 + REM-02 + REM-03 + REM-08 + REM-10 (5 of 14 queue rows). (REM-01 / F-P1-B) restore bundle matcher to on-demand intent: lib/instructions.js gates full bundle-self-check skill body on bundleMatches.length>0 (1,217-token full body becomes ~60-token stub on no-match turns); lib/bundles/matcher.js re-weights scoring (ACTION_SCORE 2→1, DEFAULT_THRESHOLD 2→3, DEFAULT_MAX_MATCHES 5→3). New tests/lib-modules/lib-bundles-matcher.test.js (8 cases) prove the audit's pathological action-soup message no longer pulls 5 bundles. (REM-02 / F-P1-C) close ESLint config-constant blind spot: new `owns_config_constants` field on chokepoints.json entries; eslint.config.js generates a per-owner no-restricted-syntax rule banning `config.X` outside the owning chokepoint module. Surfaced 18 violations including 4 previously-unknown server.js findings. (REM-03 / F-P1-D) routed 8 call sites via chokepoint helpers — added signal.getReceiveWebSocketUrl/health/quitGroup/listGroups/sendReceipt/fetchAttachmentBuffer, whatsapp.isConfigured/leaveGroup/listGroups, claude.fetchAvailableModels/runWorkTask/runBtw (with shared concurrency gate extracted from invokeClaudeCode — runWorkTask now participates), postgrest.health. (REM-08 / F-P2-D) lib/channels/index.js receipts + lib/attachments.js attachment-fetch routed via the new signal helpers. (REM-10 / F-P3-A) lib/commands/status.js consumes integrations.checkAllHealth() filtered by chokepoint module name. server.js bypasses fixed in commit f9d11fe (separate per CLAUDE.md). Restart required. |
| 2 May 202610:25 am |
koda-core |
2.91.1 |
Fix critical stuck-queue bug — Claude CLI escalating shutdown. Root cause: lib/claude.js sent SIGTERM 10s after the result event when the CLI didn't exit cleanly, but if the child had spawned MCP server children that hadn't closed their stdout pipes, child.on('close') never fired. The outer Promise hung forever, server.js's finally block (which calls messageQueue.releaseGroup) never ran, and subsequent Signal messages queued silently behind a dead claim. Reproduced in core-2026-05-01.log at 22:57:14Z — three messages (Anything left?, What are you doing, /usage) were queued and dropped before /reset wiped state. Fix: escalating shutdown chain — 10s SIGTERM (graceful), 20s SIGKILL (hard, MCP children may be blocking wait()), 30s force-resolve the Promise (destroy stdio, unref child). The 30s force-resolve is the safety net: even if SIGKILL doesn't produce a close event (zombie process, stdio inherited by an unkillable subprocess), the group claim still gets released and Signal messages keep flowing. Added `resolved` flag in close/error/timeout/force-resolve paths to prevent double-resolve on the outer Promise. Watchdog timers tracked in `watchdogTimers[]` and cleared on close/error so they don't leak. Defense in depth — the previous code path handled SIGTERM but not the case where SIGTERM was ineffective. |
| 2 May 20269:30 am |
koda-core |
2.91.0 |
EIP §5 cleanup batch 3 — four findings closed in one auto-mode plan (M3, L1, L5, L6). 13 steps, 1 Koda Core restart, 3 Tier 2 worker deploys, 1 VS Code Bridge rebuild + vsix install. (M3) New shared module apps/_shared/worker/rate-limit.js — pure-JS in-memory token-bucket factory rateLimit(key, capacity, refillPerSec) → { ok, retryAfterSec }. Wired into authenticateAdmin in all three Tier 2 worker middlewares (det22, usermgmt, koda-dashboard) with capacity=30, refill=0.5/s (30/min). Keyed by CF Access common_name when present, else hash of the bearer token. On refusal: 429 with Retry-After header. Verified end-to-end via HTTP/2 multiplex burst tests (32 calls in <1s → first 30 succeed, last 2 return 429). Per-isolate (V8) bound — fine for single-instance Workers; multi-isolate scaling would need a Durable Object/KV upgrade later. (L1) koda-vscode-bridge RunCommandTool.exec gained COMMAND_DEFAULT_ALLOWLIST (git, npm, node, ls, pwd, cat, grep, rg, bun, wrangler) + COMMAND_DENYLIST_SUBSTRINGS (rm -rf, sudo, curl, wget, nc, ssh, dd, format, > /dev/). Denylist checks first (substring match anywhere in command), then allowlist by basename of first token. KODA_BRIDGE_ALLOWED_COMMANDS env var overrides the allowlist. Refusal logs to outputChannel + returned to caller as toolResult.error. vscode-bridge bumped 0.2.5 → 0.3.0; vsix built + installed (window reload required to activate). (L5) /status gained a 'claude-settings' subsection (owner-only) reading ~/.claude/settings.json (graceful on missing/unreadable) and reporting permissions.defaultMode, skipDangerousModePermissionPrompt, model, autoUpdatesChannel, plus the constant '--dangerously-skip-permissions' flag from lib/claude.js. Module-load-time one-shot audit emission 'claude.dangerous_settings' if any flag is dangerous (suppressed via module-level seen flag for repeats). Hot-reloaded. (L6) New chokepoint lib/playwright.js — exports launch(opts), launchPersistent(profileDir, opts), createContext(browser, opts). Owns: stealth-plugin attach (lazy require, only when needed via { stealth: true }), default args ['--no-sandbox', '--disable-setuid-sandbox'], profile dir resolution (explicit > PLAYWRIGHT_PROFILE_DIR env > ~/.koda/playwright-profile), default viewport 1280x800, SingletonLock cleanup before launchPersistentContext, fire-and-forget audit row emission (action='playwright.launch', detail=callerHint or stack-walked caller). Migrated all 7 known callers (scripts/web-control.js, scripts/pw-browse.js, lib/usage.js, lib/document-generator.js, scripts/wandi-control.js, scripts/render-diagrams.js, scripts/open-browser.js) — re-grep confirms zero direct 'playwright' imports outside the chokepoint. Registered in docs/chokepoints.json. ESLint flat config auto-generates no-restricted-syntax rule from the manifest — verified to fire on bypass attempts. Plan executed in auto mode. |
| 2 May 20268:53 am |
vscode-bridge |
0.3.0current |
EIP §5 L1 closure — RunCommandTool.exec gained command allowlist + denylist gates. COMMAND_DEFAULT_ALLOWLIST = ['git','npm','node','ls','pwd','cat','grep','rg','bun','wrangler'] (basename of first token must match). COMMAND_DENYLIST_SUBSTRINGS = ['rm -rf','sudo','curl','wget','nc ','ssh ','dd ','format','> /dev/'] (substring match anywhere in command — checked first, overrides allowlist). KODA_BRIDGE_ALLOWED_COMMANDS env var overrides the allowlist (comma-separated). Refusal logs to bridge outputChannel AND returns error to caller as toolResult.error. vsix built + installed via 'code --install-extension --force koda-vscode-bridge-0.3.0.vsix' — VS Code window reload required to activate runtime. Closes 'bridge trusts whatever VS Code's language model asks for' finding. |
| 1 May 202611:30 pm |
usermgmt |
1.8.3 |
EIP §5 M3 closure — admin endpoints rate-limited at 30/min per principal via shared apps/_shared/worker/rate-limit.js token-bucket. authenticateAdmin in src/worker/middleware.js calls rateLimit(principalKey, 30, 0.5) after successful auth (CF Access common_name match OR bearer match path). principalKey = CF common_name when present, else SHA-256 hash of bearer token. On refusal: returns { ok: false, rateLimited: true, retryAfterSec, error: 'rate limited' } from middleware; admin.js routes that to a 429 Response with Retry-After header. Verified via HTTP/2 multiplex burst test: 32 sequential calls in <1s → first 30 succeed (200), last 2 return 429 with Retry-After. Per-isolate (V8) bound; sufficient for single-instance Worker. Worker version a59c5d68-2cc5-4e76-90ee-5f03d4aa4a91. |
| 1 May 202611:15 pm |
koda-dashboard |
1.3.2current |
EIP §5 M3 closure — same rate-limit wiring as det22/usermgmt. Per-principal token bucket on admin endpoints (30/min, refill 0.5/s). 429 mapping in admin.js applies to BOTH the optional-auth /health path and the auth-required paths. Verified via burst test (33 calls → 30 OK + 2 × 429 with Retry-After). With this and the prior 1.3.1 EXPECTED_SERVICE_TOKEN_CN landing, koda-dashboard's worker now matches det22/usermgmt for auth + rate-limit posture. Worker version 3608fd5c-7d3a-49b7-b70e-c1b35e8dc8b4. |
| 1 May 202611:00 pm |
det22 |
1.10.3current |
EIP §5 M3 closure — same rate-limit pattern as usermgmt 1.8.3. authenticateAdmin wraps the admin endpoint with token-bucket gate (30 calls / 60s, refill 0.5/s) keyed on CF Access common_name or bearer hash. 429 + Retry-After mapped at the admin.js handler boundary. Verified via burst test (32 calls → 30 OK + 2 × 429). Worker version 0ef34758-e2f3-49b2-a83a-1c7a9d50c4e2. |
| 1 May 202610:48 pm |
koda-core |
2.90.0 |
EIP §5 cleanup batch 2 — five Medium/Low items closed in one auto-mode plan, four restarts + one docs-only commit. (M4) lib/voice-transcribe.js no longer hard-codes /opt/homebrew/bin/whisper-cli, /opt/homebrew/bin/ffmpeg, /Users/koda/mcp-servers/models — replaced with WHISPER_BIN / FFMPEG_BIN / WHISPER_MODELS_DIR env vars defaulting to the existing paths so behaviour is unchanged on this host but moving to another machine becomes a config change, not a code change. (M9) lib/file-share.js gained a checkShareAccess(ip, share) gate with per-IP token-bucket rate limiting (SHARE_RATE_MAX_PER_IP=30 over 60s) and per-token download cap (SHARE_TOKEN_DOWNLOAD_CAP=100). server.js /share/:token route now calls the gate before resolveCorefile; refusal returns HTTP 429 with Retry-After + audit action='share.rate_limited' (reason=ip_rate or token_cap). Both bounds env-overridable. (M10) lib/api/chat.js fixed the PWA confused-deputy: ALLOWED_TRANSPORTS=['','pwa','signal'] enforced via validateTransport() helper applied to /send, /command, /plan, /active. Refuses unknown values with HTTP 400 + audit 'api.chat.cross_channel_refused'; audit-logs every signal-transport request with 'api.chat.cross_channel' for visibility (not denial — PWA legitimately uses transport='signal' to resume Signal sessions). (L2) lib/api/auth.js /token/service now supports CF_OWNER_SERVICE_TOKEN_CN env-or-config allowlist (comma-separated common_names). When set, after the existing client ID/secret match, also requires inbound Cf-Access-Jwt-Assertion whose common_name claim is in the allowlist. Backward-compat: env unset preserves prior behaviour. Mismatch returns 401 + audit 'api.service_token.cn_mismatch'. (M6) docs/key-rotation.md added — 335-line runbook for rotating the keysvc credential key: why rotate, preconditions (off-peak, Koda Core stopped, key + DB backup), procedure (decrypt-all → atomic key-file swap → re-encrypt → verify → restart), rollback paths for each phase, post-conditions, what's NOT covered (cipher migration, multi-key keysvc, key escrow). Closes the 'no documented rotation procedure' part of M6 — actual rotation script remains an at-rotation-time build per the runbook. Plan executed in auto mode. |
| 1 May 202610:05 pm |
koda-core |
2.89.0 |
EIP §5 cleanup batch 1 — four Medium/Low items closed in one auto-mode plan, three restarts + one hot-reload. (M5) lib/file-share.js shareFile() now resolves and validates the source path against an explicit allowlist (SHARE_ALLOWED_ROOTS = [config.KODA_DIR]) before sharing — the path-traversal guard moved out of the command layer (lib/commands/share.js) into the chokepoint itself, so any future caller of shareFile() inherits it. fs.realpathSync resolves symlinks; prefix-match against the allowed root rejects anything outside. Defence-in-depth: command-layer check retained. (M7) lib/sync.js getAdminApiKey() previously read .secrets/admin_api_keys from disk on every call. Now caches Map<appName, key|null> with lazy fs.watch invalidation: watcher registers on first call after a successful read, clears the cache on file change, gracefully self-cleans on watcher errors. Caching null is intentional (avoids re-reading for apps with no admin key). Cache size bounded by Tier 2 app count (~3). (L3) lib/usermgmt-client.js _fetch() now wraps requests with 3-attempt exponential backoff (250/500/1000ms) for network errors and 5xx; 4xx surface immediately so auth/permission errors aren't masked. assignRole() refactored to route through _fetch via new extraHeaders param (X-Directed-By), unifying the retry path. lib/sync.js fallback to direct Postgres push remains as last-resort. (L7) lib/commands/cred.js /cred reveal now prepends an auto-expiry warning with the 60s countdown and schedules a follow-up 'reveal expired — delete the message above' notice. signal-cli-rest-api 0.98 (json-rpc mode) doesn't expose remote-delete via REST (probed /v1/rpc → 404, v2/send capabilities only quotes+mentions), so this is a soft time-box; the real defence remains owner-only + DM-only + audit-logged. Hot-reloaded. (M1, L4 doc-stale) Marked resolved in audit doc — M1 satisfied by docs/chokepoints.json + lib/integrations.js + /status integrations from EIP Week-2 #12; L4 satisfied by EIP #10 Layer A buffer + Layer B drain + stuck-buffer DM. Plan executed in auto mode. |
| 1 May 20267:58 pm |
koda-dashboard |
1.3.1 |
EIP Q1 #20b closed — wrangler.toml [vars] now declares EXPECTED_SERVICE_TOKEN_CN = 'c2ce2792eb99295cd3e30ea40acf673f.access' (the koda-dashboard CF Access service-token client_id). Worker redeployed; live env binding confirmed. authenticateAdmin() in src/worker/middleware.js validates Cf-Access-Jwt-Assertion.common_name against this value, blocking cross-app spoofing. Smoke tests pass: (a) matching service token → handler-level 400 (auth passed); (b) det22 service token → 401 with 'service token mismatch'; (c) bearer fallback retained. Per-app identifier is public, not secret — [vars] is the right home (the matching secret half lives in the cred store on Koda Core, domain 'cf-access-koda-dashboard'). Version a5928783-bc74-4698-ad94-7068bafb9b9c. |
| 1 May 20267:58 pm |
koda-core |
2.88.0 |
EIP backlog cleanup — three Week-1/Week-2 items closed in one auto-mode plan, two restarts. (#10 Week-1) Layer-B audit-pending drainer landed at scripts/scheduled-jobs/audit-drain-watchdog.js: atomic-renames logs/audit-pending.jsonl to .draining.<ts> so concurrent appendFile from lib/audit.js lands in a fresh file, POSTs each row to PostgREST audit_log, rotates to .applied.<ts> on full success (cleaned up after 7 days), appends failed rows back to live file otherwise. Stuck-buffer alarm DMs the owner if the live file is >30min stale OR >100 lines, with 30min re-alert suppression via tmp/audit-drain-state.json. Registered as scheduled_tasks row id=8 'Audit Pending Drain', cron */5 * * * *. Documented bypasses added to docs/chokepoints.json (postgrest, signal — must work when Koda Core is down). (#14 Week-2) lib/inbound-anomaly.js inspectInboundMessage was sitting unwired since landing; server.js:processMessage now calls it fire-and-forget right after messageText is finalized — never blocks, never mutates, never throws to the caller. Hits land in audit_log with action='inbound.anomaly' and DM the owner if a non-owner sender trips a rule. (#16 Week-2 / §5 M8) Defence-in-depth on top of ALLOWLIST_ROOT: scripts/scheduler-allowlist.txt enumerates 7 active scripts (anomaly-watchdog, audit-drain-watchdog, daily-task-review, reconcile-apps, reset-timeout, watchdog, weekly-memory-audit). lib/scheduler.js loads the file at module init into a Set; executeTask() refuses any script-type task whose basename isn't in that set with action='scheduler.task.refused', reason='not in allowlist' — same audit + owner-DM path as the existing dir-out refusal. Not hot-reloadable (cron map is in-memory) — full restart required to pick up edits. Two restarts (server.js step, lib/scheduler.js step). Plan executed in auto mode. |
| 1 May 20266:10 pm |
koda-core |
2.87.0 |
EIP audit CRITICAL/HIGH cluster — H1 + H3 + H8 closed; C1 confirmed already resolved (audit was stale at write time). Eight-step plan, three restarts. (H1) lib/scheduler.js:_executePrompt was spawnSync('claude', ['-p', '--output-format', 'json'])'ing directly, skipping the lib/claude.js chokepoint (no concurrency gate, no /stop interruptibility, no model selection). Replaced with await invokeClaudeOneShot(prompt, {outputFormat:'json', timeout, cwd, env:{KODA_SCHEDULED:'1'}}); session-id capture preserved for the expects_session guardrail; net -19 lines, dropped spawnSync from imports (commit 31764c9). (H3) The remaining WAHA bypass — server.js:1534 axios.get(${WAHA_BASE_URL}/api/default/groups/${id}, {X-Api-Key, timeout:5000}) for fetching a group's display name — was migrated. lib/channels/whatsapp.js gained async getGroupSubject(groupId) that wraps the WAHA group endpoint with wahaHeaders() auth and 5s timeout (commit 10a6402, hot-reloaded). server.js now calls await channels.whatsapp.getGroupSubject(waFrom) and reads .subject; orphaned WAHA_BASE_URL constant deleted; -9 lines server.js (commit bd1592a, restart). (H8) lib/cloudflare.deployPages already owned the chokepoint (cred-store token via getWranglerToken, env injection, audit logging, stdout URL parsing) but scripts/deploy-watch.sh:56 still shelled out to wrangler directly. Added scripts/cf-deploy-pages.js — a 60-line Node wrapper mirroring the keysvc-encrypt.js pattern: requires lib/cloudflare.deployPages, takes <site-dir> <project> [branch] argv, prints URL on success, exits 2 on chokepoint failure (commit 6257776). Switched deploy-watch.sh to invoke that wrapper (commit 893043c). No raw wrangler calls remain in the repo outside lib/cloudflare.js. (C1) Discovered during investigation that apps/_shared/worker/cf-access-jwt.js already exists and uses jose.jwtVerify against createRemoteJWKSet for koda-systems; all three Tier 2 workers (det22, usermgmt, koda-dashboard) import verifyAccessJwt from it. Audit doc was stale on this point — corrected. Doc updates: external-integration-audit.md C1/H1/H3/H8 marked resolved with diagnoses; bypass table at §4.2 struck through three rows; §3.1 Cloudflare row updated to show lib/cloudflare.js as chokepoint; §3.4 scheduler narrative updated; Week-1 punch list items 5/6/8/9 struck through; surprise §7 'validateCfJwt' bullet struck through; -25 +16 lines (commit 226fcc5). Three restarts (steps 2, 4 — step 3 hot-reloaded). Plan executed in auto mode. |
| 1 May 20266:10 pm |
deploy-watch |
1.1.2current |
Route Pages deploy through the cloudflare-deploy chokepoint via scripts/cf-deploy-pages.js Node wrapper. Replaced raw `wrangler pages deploy "$SITE_DIR" --project-name "$PROJECT" --branch main` with `node "$KODA_DIR/scripts/cf-deploy-pages.js" "$SITE_DIR" "$PROJECT" main`. CF_TOKEN export retained as fallback — lib/cloudflare.getWranglerToken reads from the encrypted credentials table (domain='cloudflare-wrangler') first and only consults env if PostgREST is unreachable. Net effect: deploy-watch now inherits the chokepoint's audit logging, env injection (CI=1), maxBuffer=16MB, and structured error handling. EIP H8 closure. Commit 893043c. |
| 1 May 20265:30 pm |
koda-core |
2.86.0 |
EIP audit finding H6 resolved — deleted the ENC: env-var encryption scheme. Investigation found two layers of brokenness: (a) the server.js:12-35 boot IIFE inline-decrypted ENC:-prefixed env vars by reading .secrets/credential_key directly, but that file was deleted in 2.83.0 (keysvc cutover) so any ENC: value would have hit ENOENT; (b) /config set never actually wrote ENC: prefixes anyway — it called encryptCredentialAsync() without format:'env', defaulting to credential format which has no prefix. The decrypt path was unreachable in practice. Plus, the only two SENSITIVE_KEYS (SIGNAL_CLI_REST_API, POSTGREST_URL) were localhost URLs that don't need encryption. Real secrets live in the credentials table via /cred, encrypted through keysvc. Changes: lib/commands/config.js — removed encryptCredentialAsync import, SENSITIVE_KEYS array, the conditional encryption at storeValue, and the redaction in /config get + /config list (commit 6283b3b, hot-reloaded). server.js — deleted the decryptEnvSecrets() IIFE (-26 lines, commit d28a5cc, restart). Doc updates: external-integration-audit.md marked H6 resolved + struck-through the bypass row + struck-through the Week-1 #7 recommendation; install-notes ENC: auto-decrypt claim removed (commit 8d4689e). Net effect: one fewer chokepoint bypass, /config behaviour now honest (cleartext-only operational tunables; secrets go to /cred). One restart, no regressions. |
| 1 May 20265:00 pm |
koda-core |
2.85.0 |
EIP Q1 follow-up #3 task #4b — removed sync crypto API and lib/keysvc-bridge-cli.js. After task #3 zero'd the sync-bridge call rate on hot paths, a re-audit found 3 missed sync callers (lib/api/credentials.js create-flow, scripts/scheduled-jobs/watchdog.js getResendApiKey, scripts/get-cred.js) plus 2 already-async scripts (scripts/wandi-control.js, scripts/web-control.js) and tests/run_tests.js. Path A: migrated each to decryptCredentialAsync (one file per commit, 14555bd / 4ab975c / e07a6e7 / 97308fc / b1199cc / 7df7942), introduced scripts/keysvc-encrypt.js as the standalone async helper for shell-script consumers (commit dfc418c), updated scripts/cred-set.sh to call it (commit fd06a40), then deleted the sync surface from lib/crypto.js (commit c3ce24c — removed encryptCredential/decryptCredential/decryptEnvValue/getCredentialKey/_bridgeCall/_encryptWithFileKey/_decryptWithFileKey/_warnFallback/getCredKeyPath/cachedCredentialKey/BRIDGE_CLI/NODE_BIN, kept encryptCredentialAsync/decryptCredentialAsync/maskCredential/KEYSVC_REQUIRED/KEYSVC_DISABLED) and removed the now-orphan lib/keysvc-bridge-cli.js (commit 7c77b5e — 78 lines). KEYSVC_DISABLED behaviour changed: previously bypassed keysvc and used file-key fallback; now throws EKEYSVC_DISABLED since file-key fallback was deleted in EIP #19 phase 3 (2.83.0). Verification: 61/61 tests pass, /health ok, get-cred 40-char plaintext, wandi-control returns Active+$43.60+~13days via direct method, cred-set roundtrip MATCH via new keysvc-encrypt.js path. Two restarts (steps 2, 11). |
| 1 May 20264:00 pm |
koda-core |
2.84.0 |
EIP Q1 follow-up #3 — sync→async crypto migration. Fixed scripts/wandi-control.js, scripts/web-control.js, tests/run_tests.js (broken since keysvc cutover 2.83.0 — local AES blocks read deleted .secrets/credential_key); replaced with require('lib/crypto') so the sync API routes through keysvc. Migrated 6 lib/* hot-path callers (auth-headers, calendar, cloudflare _resolveApiToken, intents _getServiceToken cache, api/push initializeVapid, api/credentials with Promise.all over rows.map) and 2 commands (cred 4 sites, config 1 site) to decryptCredentialAsync. server.js services injection now exposes encryptCredentialAsync/decryptCredentialAsync; removed dead getCredentialKey() warn-probe at server.js:1666 (predated keysvc, logged a benign ENOENT warn on every restart since 2.83.0). Measured 24h sync-bridge call rate at 66/day pre-task-#3 and 0/day post-task-#3 (audit_log requestor='koda-core-sync-bridge'); decided to keep lib/keysvc-bridge-cli.js and the sync API in lib/crypto.js as a defensive fallback for CLI scripts (scripts/get-cred.js, scripts/cred-set.sh, scripts/wandi-control.js, scripts/web-control.js, tests/run_tests.js) — the bridge adds ~30-50ms per call but is no longer hit on hot paths. Three restarts (tasks #2, #3) with no regressions; rollback path via git revert documented in each task. Coverage: ~25 call sites across 13 files. Commits 784117a/5f56769/aee3da3 (scripts), 2df794d (lib/*), 38b0037 (commands+server.js). |
| 1 May 20263:00 pm |
koda-core |
2.83.0 |
EIP Q1 carryover trio landed in one minor — #281 daily Tier 2 permission sync cron, #20b koda-dashboard CF Access service-token migration, #19 phase 2/3 keysvc cutover. (1) **#281 daily reconcile cron**: inserted scheduled_tasks row id=7 'App Reconciliation Daily' (cron `0 3 * * *`, command_type=script, command=`scripts/scheduled-jobs/reconcile-apps.js`) — fires 03:00 Australia/Melbourne nightly to detect and sync Postgres↔D1 user/role drift across all Tier 2 apps via the existing /api/sync/permissions chokepoint. Moved scripts/reconcile-apps.js → scripts/scheduled-jobs/ to satisfy the scheduler's realpath-resolved allowlist; updated relative require paths and header doc. First diagnostic run uncovered two issues fixed in-flight: (a) usermgmt had no workspaces/workspace_surfaces row at all (caused 'No workspace linked' error in sync) — inserted workspaces row name='usermgmt', workspace_surfaces row mapping app→workspace, and owner role on the usermgmt workspace; re-ran reconcile, usermgmt now syncs cleanly (1 user). (b) det22 D1=5 vs Postgres=1 user mismatch — root-caused to handleSyncPermissions in apps/det22/src/worker/admin.js doing upsert-only with no stale-row pruning; flagged as a separate follow-up plan (worker change + deploy, out of scope here). Cron activates on next Koda Core boot since the in-process scheduler reads from DB at startup. (2) **#20b koda-dashboard CF Access**: generated dedicated service token via Cloudflare API POST /accounts/{id}/access/service_tokens (name='koda-dashboard', client_id ending `.access`, expires 2027-04-30), stored via cred-set.sh as cf-access-koda-dashboard with AES-256-GCM-encrypted `id:secret` payload (verified decrypt round-trip). Worker-side: added authenticateAdmin() to apps/koda-dashboard/src/worker/middleware.js mirroring det22's pattern (CF Access JWT primary with EXPECTED_SERVICE_TOKEN_CN === jwt.common_name match, ADMIN_API_KEY bearer fallback for break-glass); apps/koda-dashboard/src/worker/admin.js now uses it for /admin/health (auth-decides-detail-level — unauth responds shape-only, auth includes recent_audit_count + service_token_authenticated flag) and ALL other /admin/* endpoints. Added EXPECTED_SERVICE_TOKEN_CN = `<client_id>` to wrangler.toml [vars]; deployed via lib/cloudflare.js.deployWorker. **Caught a sleeper bug**: koda-dashboard had been deployed without the CF_ACCESS_AUD secret entirely — apps/_shared/worker/cf-access-jwt.js throws if env.CF_ACCESS_AUD unset, so first request returned Worker error 1101 (worker_threw_exception). Fixed via lib/cloudflare.js.putSecret CF_ACCESS_AUD = `39bd00b22989f16a5a00e50315b6ce1788a7acfadb612cc7e16a609ed4931ab6` (wildcard *.koda.systems aud tag) and updated the *.koda.systems CF Access app 'Service Token Access' policy to include the new token_id. Final verification: dash.koda.systems/admin/health returns HTTP 200 with `recent_audit_count:3` proving the service-token auth path resolves correctly. lib/sync.js was already calling getCfAccessHeadersForApp('koda-dashboard') so no Koda Core code change was required for the dashboard side. Commits cf998e5 (worker auth), 1e65c1d (wrangler.toml + AUD secret + policy). (3) **#19 phase 2/3 keysvc cutover**: previously all credential AES-256-GCM encrypt/decrypt in Koda Core read the key from `.secrets/credential_key` (perm 0600, koda-readable) — this phase moves the key into the kodasvc trust domain at /var/lib/koda-keysvc/key (perm 0400, kodasvc-only) and routes every encrypt/decrypt through the keysvc sidecar over a Unix socket, then deletes the on-disk key from the koda user. New `lib/keysvc-client.js` — JSON-line socket client (NOT HTTP — single-line JSON over net.Socket, \n-delimited; corrected initial assumption); 7-scenario self-test passed including the critical legacy-compat check that ciphertext produced by lib/crypto.js.encryptCredential decrypts correctly via keysvc.decrypt, proving keyfile byte-identity. Typed error codes (ETIMEDOUT, ENOENT, EKEYSVC_ERROR, EKEYSVC_PROTOCOL). Refactored `lib/crypto.js`: sync encryptCredential/decryptCredential now route through keysvc via new `lib/keysvc-bridge-cli.js` (one-shot spawnSync subprocess, ~30-50ms overhead per call — preserves the sync API contract used by ~15 callers in Koda Core without forcing every call site to async); new async exports encryptCredentialAsync/decryptCredentialAsync skip the spawn for new code paths. KEYSVC_REQUIRED env gate: unset = warn mode (try keysvc, fall back to file-key on error with one-shot warn log per direction); KEYSVC_REQUIRED=true = required mode (any keysvc error throws, file-key fallback unreachable). Phase 2 activation: appended KEYSVC_REQUIRED=true to .env directly (not via /config since CONFIGURABLE_KEYS allowlist excludes platform infra knobs); restarted Koda Core via launchctl kickstart -kp gui/$UID/com.koda.core; post-restart audit log shows 28 successful decrypts + 3 successful encrypts from requestor 'koda-core-sync-bridge' with ZERO failures, including boot-time decryption of CLOUDFLARE_API_TOKEN/CF_ACCESS_CLIENT_ID/CF_ACCESS_CLIENT_SECRET — would have thrown if any path had bypassed keysvc. Phase 3 keyfile removal: confirmed shasum identity between /var/lib/koda-keysvc/key and .secrets/credential_key (cdbadb4f54ca2bee64b73e21e25043715215fd012f667e27cedc2788c4497fea on both); soft-removed via mv → tmp/credential_key.removed-step9-1777601221.bak (rollback-friendly, hard-deletable in a few days); fresh node process spawned with KEYSVC_REQUIRED=true loaded from .env via dotenv decrypted all 15 stored credentials → 15/15 ok 0 fail, proving the file-key fallback path is now dead (file gone + KEYSVC_REQUIRED throws on attempt). Running Koda Core process unaffected (cached buffer in memory irrelevant since KEYSVC_REQUIRED=true makes fallback unreachable); on next restart the server.js:1666 getCredentialKey() probe will throw + log a benign warn line — non-fatal because wrapped in try/catch. Rollback path documented: restore tmp/credential_key.removed-*.bak → /Users/koda/koda/.secrets/credential_key + remove KEYSVC_REQUIRED line from .env + kickstart restart. Commits 2d5c569 (keysvc-client), eff105f (crypto refactor + bridge-cli). All three EIP carryovers complete; Q1 #19/#20b/#281 closed. |
| 1 May 20261:20 pm |
det22 |
1.10.2 |
EIP Q1 follow-up #1 — fix stale-row drift in handleSyncPermissions (apps/det22/src/worker/admin.js). Daily reconcile cron (#281) had been reporting det22 D1=5 vs Postgres=1 user mismatch because the sync handler was upsert-only with no DELETE pass. Added stale-row pruning after the upsert loop with three guards: (1) skip entirely if incoming users.length === 0 (defends against accidental wipe via a malformed payload), (2) only delete is_proxy=0 rows (proxy users are admin-created inside the app, never in the Postgres canonical list), (3) emit audit_log entry per deletion BEFORE the DELETE so user_id/email/reason are captured (action='sync.user_deleted', actor='koda-core', channel='sync'). FK behaviour on det22: rsvps.user_id and availability.user_id ON DELETE CASCADE so those auto-clean; activities.created_by has no cascade but the column is nullable so dangling references don't error in D1 — leaving them is acceptable since activities don't strictly need a valid creator post-creation. Response payload now includes a `deleted` field. Worker deployed via lib/cloudflare.js.deployWorker (audit row 3508, 8.4s). Verification: post-deploy syncPermissionsToApp('det22') returned ok+synced=1, /admin/health user_count dropped 5→1, recent_audit_count=4 (the 4 stale rows pruned), alerts list empty. Drift cleared. Commit c5af0c5. |
| 1 May 20261:20 pm |
usermgmt |
1.8.2 |
EIP Q1 follow-up #1 — same DELETE-not-in pass added to apps/usermgmt/src/worker/admin.js handleSyncPermissions (cross-fix to keep both Tier 2 worker handlers consistent). usermgmt's app_users had no stale rows at the time of fix (recent_audit_count=0 after sync) but the bug was structurally identical — upsert-only with no pruning — so this is preventative. usermgmt's app_users has no inbound FKs (managed_users and role_assignments use a separate managed_user_id PK), so plain DELETE is safe with no cascade considerations. Same three guards (empty-list skip, is_proxy=0 filter, audit_log per deletion) and same `deleted` field in response payload. Worker deployed via lib/cloudflare.js.deployWorker (audit row 3509, 9.5s). Post-deploy verification: sync ok, user_count=1, alerts empty. Commit 60b7b23. |
| 1 May 20261:00 pm |
koda-core |
2.82.0 |
EIP Q1 #21 closeout — bundle gate hard-fail flip + /bundles reload alias. Two related changes that finalise the retrofit arc (P1-P4 shipped 13/13 bundles in 2.78.0-2.81.0; this is the gate-tightening follow-up). (1) `lib/intents.js.checkBundleGate` Case C flipped from soft-fail to hard-fail. Before: bundle absent → if manifest.bundle_required=true refuse, else log warn + audit `intent.bundle_missing` + dispatch (silent allow — transitional state during the retrofit). After: bundle absent → unconditional refusal, log warn `dispatch gate refused (no bundle for app)`, audit `intent.refused` with reason `no_bundle_registered`, DM owner via _shouldDmBundleRefusal/_dmOwnerBundleRefusal pattern (DM message includes the fix path: `Add bundles/X.yaml then /bundles reload.`), return {allowed:false, refused:true, reason}. The bundle_required manifest opt-in branch dropped entirely — irrelevant now that all Tier 2 apps must declare a bundle. JSDoc Cases comment block updated to reflect the simplified two-branch logic (Case A allow, Case B refuse-action-missing, Case C refuse-bundle-missing — all three with consistent refusal semantics on miss, single allow branch on hit). (2) `lib/commands/bundles.js` adds `/bundles reload` subcommand as an alias for the existing `validate`. Both call `registry.refresh()` → `loadAllBundles()` → bundles.clear() + re-scan disk; identical behaviour, just a more discoverable name when an owner is debugging a Case-C refusal and naturally searches for 'reload' rather than 'validate'. Shared code path via `if (sub === 'validate' || sub === 'reload')` block; reply title varies ('Bundle registry validate' vs 'Bundle registry reload') based on which alias was invoked. HELP_TEXT and top-of-file JSDoc subcommand list both updated. Hot-reloaded — no restart required to activate the alias itself; restart for this version is solely for the lib/intents.js Case C flip. **Safety check confirmed before flip**: of the 4 koda_apps DB rows (console, koda-dashboard, usermgmt, det22), only usermgmt and det22 produce intents — both have bundles. console is handled by the tier-check before the gate (returns 'console tier not implemented'); koda-dashboard has empty manifest with no intents declared so never reaches the gate. No current Tier 2 app refused by the flip. **Repeatable fix path** for any future missed integration: owner sees DM → identify chokepoint module → create bundles/X.yaml from SCHEMA.md template → validate via parser smoke test → commit via self-modify.sh (no restart for YAML) → /bundles reload. **Anti-silent-failure design preserved**: gate explicitly returns {allowed:true} on registry lookup throw (line 105 catch — fail-open on internal errors), so a registry hiccup never silently denies. Refusals surface 5 ways: chat reply to requestor, owner Signal DM (30-min app.action dedupe, resets on restart), audit_log row, core.log warn line, anomaly watchdog over audit_log volume. Commits TBD-versions / TBD-bundles-reload / TBD-intents. |
| 1 May 202612:00 pm |
koda-core |
2.81.0 |
EIP Q1 #21 P4 retrofit + activation — final 2 bundles shipped (web-control, pdf-generation), then single restart activates all 8 P2+P3+P4 inactive bundles together. Registry coverage 11/13 → 13/13 (FULL — every external integration target now has a registered bundle), 54 → 62 declared actions. (1) `bundles/web-control.yaml` — 3 actions (wandi_status read-only, wandi_pause destructive, wandi_unpause destructive); integrations: [lib/commands/wandi.js, scripts/wandi-control.js]; chokepoint_fn=runScript (internal helper in wandi.js — bundle documents the script-wrapper indirection per ADR-019: single-vendor script wrappers stay as slash commands rather than Tier 2 koda_apps until 5+ accumulate); documents the Wandi/Launtel dispatch (slash command → execFile node scripts/wandi-control.js with 60s timeout → dual-path: direct HTTP via cached session cookies primary, Playwright browser automation fallback when Launtel UI changes), exit code semantics (0=direct ok, 1=fallback ok handler attaches _fallback:true, 2=both failed throws with stderr), site config layout (data/web-control/wandi-internet/config.json + session-state.json + http-cookies.json with 30-min TTL), $5.50 baseline reconnection fee for unpause (flag if config.json drifts), the 'push through unpause fully without pausing for confirmation' protocol per memory (owner has already opted in by typing /wandi unpause; mid-flow confirms are friction); anti-patterns: don't axios residential.launtel.net.au directly (script owns CSRF/cookies/UA/dual-path/exit-codes), don't run Playwright manually for status (~3s direct vs ~20s Playwright), don't dispatch unpause from chat without explicit owner confirm (Claude-initiated, $5.50 non-recoverable), don't loop pause/unpause in cron ($132/day if hourly), don't treat exit 1 as failure (operation completed), don't hardcode wandi-specific values, don't extend bundle to other ISPs without script refactor. (2) `bundles/pdf-generation.yaml` — 5 actions (generate_document dispatcher, generate_md, generate_pdf, generate_pptx, generate_docx — format-specific helpers exposed for callers that already know the format); integrations: [lib/document-generator.js, lib/pdf-templates/index.ts]; chokepoint_fn maps to generateDocument/generateMD/generatePDF/generatePPTX/generateDOCX exports; NOT destructive (generation produces new files at new paths, never overwrites — duplicate filenames get numeric suffix); documents the React-PDF Mockup D v7 template library lock-in per memory (always import from lib/pdf-templates barrel: palette/styles/docTypeConfig/pillVariants/statusPillConfig + KodaPage/GradientHeader/SimpleFooter/Callout/Pill/StatusPill/Bullet/DataTable/DiagramContainer; never re-declare fonts/colours/styles/components inline; redundant scripts/pdf-template.tsx was deleted for this reason), 4 canonical doc types (ANALYSIS/RESEARCH/TRANSCRIPT/REPORT) with example-image manifest workflow per memory (check hash → regenerate if stale → present to user → generate), Inter+Source Serif font discipline (never switch mid-doc), the @react-pdf/render Canvas painter patch (linearGradient/radialGradient must return gradient object — patch-package'd, required by GradientHeader, removal breaks header silently), two PDF paths (generatePDFReactPDF primary, generatePDFPlaywright fallback only when React-PDF refuses), auto-share via lib/file-share.js with default site/shared/<guid>/index.html (NEVER publish to site/ root per memory — only on explicit owner request to a named path), 30-day CF Pages expiry, deploy-watch trigger; anti-patterns: don't re-declare template primitives inline, don't bypass file-share for sharing, don't publish to site/ root, don't switch fonts mid-doc, don't use Playwright as primary path, don't skip example-image manifest check, don't iterate PDF mockups via koda.systems URL (use Signal attachments per memory — Safari rendering can drift), don't call pptxgenjs/docx/@react-pdf/renderer directly, don't remove the Canvas patch. (3) Single restart via `launchctl kickstart -k gui/$UID/com.koda.bridge` activates all 8 inactive bundles from P2+P3+P4 (tasks, scheduler, transcription, cloudflare-deploy, vscode-bridge, web-push, web-control, pdf-generation) — boot-time bundle registry preload reads bundles/*.yaml from disk, restart is the activation trigger. Post-restart: registry coverage 13/13 (FULL), 62 declared actions across 13 bundles. **Q1 #21 retrofit arc COMPLETE** across 4 phases (P1: det22+usermgmt+signal+whatsapp / P2: tasks+scheduler+transcription / P3: cloudflare-deploy+vscode-bridge+web-push / P4: web-control+pdf-generation). Every external integration target on Koda Core now routes through a registered chokepoint with EIP three-runtime defence (matcher preload at write-time, self-check skill at draft-time, dispatch gate at execution-time). Separate follow-up plan will flip the bundle gate from soft-fail (Case C log+allow) to hard-fail (refuse unregistered actions). Bundles parse cleanly through lib/bundles/parser.js; commits fd2e796 (web-control), 5f960ed (pdf-generation). |
| 1 May 202611:00 am |
koda-core |
2.80.0 |
EIP Q1 #21 P3 retrofit — 3 bundles shipped (cloudflare-deploy, vscode-bridge, web-push). Registry coverage 8/13 → 11/13 bundles, 45 → 54 declared actions. (1) `bundles/cloudflare-deploy.yaml` — 4 actions (deploy_pages, deploy_worker, put_secret, apply_d1_migrations), ALL destructive (Pages/Worker overwrite live deployment, put_secret is irreversible without external backup, D1 migrations are forward-only DDL with no `wrangler migrations rollback`); integrations: [lib/cloudflare.js]; chokepoint_fn maps to deployPages/deployWorker/putSecret/applyD1Migrations exports; documents the credential-store flow (PostgREST credentials table domain='cloudflare-wrangler' decrypted via lib/crypto.js, env fallback only as a hot-path optimisation since server.js loads it at startup), wrangler binary resolution sequence (WRANGLER_BIN env → /opt/homebrew/bin/wrangler → ~/.nvm/versions/node/v24.14.0/bin/wrangler → /usr/local/bin/wrangler → `which wrangler`), audit_log namespace (cloudflare.pages.deploy / .worker.deploy / .secret.put / .d1.migrate) emitted BEFORE throw on failure (missing audit row indicates chokepoint crash, not wrangler failure), CI=1 env flag to suppress wrangler interactive prompts, 16MB maxBuffer for large deploys, stdin-piping for `secret put` (NEVER argv — process listings leak), the `deployer` audit-actor convention ('deploy-watch' for fswatch pipeline, 'app-provisioner' for Tier 2 provisioning, 'manual:klaus' for owner-initiated), no-auto-retry policy (deploy-watch retries via next file change; manual deploys are owner-investigated); anti-patterns: don't shell out to wrangler directly, don't read CLOUDFLARE_API_TOKEN from env for new code, don't pass secret value on argv, don't auto-retry deploy failures, don't expect D1 migration rollback, don't skip audit_log writes, don't bypass for cron without bypassToken. (2) `bundles/vscode-bridge.yaml` — 4 actions (invoke_vscode, check_vscode_health, fetch_vscode_models, set_vscode_model), read-side no destructive_actions; integrations: [lib/vscode.js]; documents the Copilot routing rationale (avoiding re-implementing Copilot auth in Koda Core by running a VS Code extension on kodahost that proxies OpenAI-style requests to Copilot's authenticated session), 900s default timeout via VSCODE_BRIDGE_TIMEOUT_MS, /health vs /ready distinction (the chokepoint's checkVSCodeHealth uses /health + data.models.length>0 assertion to detect 'bridge up but Copilot dead' without hanging, since /ready hangs when Copilot is unauth'd), Copilot auth recovery sequence (SOCKS proxy on Mac Mini 192.168.100.10 → kodahost network reconfig → re-auth Copilot in VS Code), AbortController/AbortSignal.timeout enforcement, sessionId always null (bridge does not surface Copilot-side resume), state.vsCodeSelectedModel pin behaviour ('auto' clears, any other ID pins); anti-patterns: don't fetch the bridge directly without timeout/abort handling, don't curl /ready with short timeout (hangs when Copilot unauth'd), don't lower 900s default for unknown-length prompts, don't cache fetch_vscode_models across sessions, don't treat checkVSCodeHealth=false as 'bridge offline' (usually means Copilot died not bridge). (3) `bundles/web-push.yaml` — 1 action (notify_user, chokepoint_fn=pushNotifyUser), send-only not destructive; integrations: [lib/api/push.js]; documents the deliberate single-action surface (initializeVapid is boot-only; subscribe/unsubscribe/status/test are HTTP endpoints invoked by PWA frontend not server-callable), three-layer messaging context (L1 SSE foreground / L2 long-poll fallback / L3 web push background — fire only when L1+L2 absent), VAPID init (public key in .env, private key in credentials table domain='vapid-keys' decrypted via lib/crypto.js with legacy-unencrypted warning), per-subscription send loop, auto-cleanup on 410/404 (keeps push_subscriptions table from accumulating zombie rows from uninstalled PWAs), last_used_at touch on success, payload shape ({title,body,tag?,url?}) and lock-screen visibility caveat (use generic title for sensitive content, put detail behind click-through url), tag field for collapse-key dedup, lazy VAPID init pattern, never-throws contract (returns 0 on any failure so messaging layer can fall back to other channels); anti-patterns: don't call webpush.sendNotification directly (loses lazy init/enumeration/cleanup/last_used_at touch), don't iterate push_subscriptions yourself for 'more control' (cleanup is safety-critical for table-growth bound), don't put sensitive content in payload.body (lock-screen visible by default), don't push when SSE/long-poll active (gate on presence first), don't loop notify_user for batches without tag (use tag for collapse-key dedup), don't initializeVapid outside the chokepoint (race vs in-flight sends), don't treat return=0 as failure (means fall back to another channel). All 3 bundles parse cleanly through lib/bundles/parser.js (loadBundleFile validates name/version/description/integrations/permissions/destructive_actions cross-check/instructions/actions); commits 6dd9511/1d62401/33a9f8a. P3 + P2's 6 inactive bundles will activate together at next Koda Core restart (deferred to end of P4 to amortise restart cost). Two retrofits remain (P4): web-control, pdf-generation. |
| 1 May 202610:00 am |
koda-core |
2.79.0 |
EIP Q1 #21 P2 retrofit — 3 bundles shipped (tasks, scheduler, transcription). Registry coverage 5/13 → 8/13 bundles, 29 → 45 declared actions. (1) `bundles/tasks.yaml` — 7 actions (list_tasks, create_task, update_task, mark_done, delete_task, get_task_info, work_tasks), 2 destructive (delete_task hard DELETE FROM, mark_done flips status with no history table); integrations: [lib/postgrest.js, lib/commands/tasks.js]; chokepoint_fn maps to pgGet/pgPost/pgPatch/pgDelete on the `tasks` table; documents the user-facing `tasks` table schema (id/title/status/priority/scope/scope_id/labels/due_date), the global/app/group scope hierarchy + auto-resolution heuristics in resolveContextScope(), the `lib/tasks.js` background-runner naming collision (intentionally separate surface), the work_tasks side-effects (writes per-task instruction files + queue entry, flips status to in_progress); anti-patterns: don't axios PostgREST directly, don't bulk-delete in a loop (use executeBatch with single confirm), don't skip updated_at=now() on patches, don't generate instruction files for already-done tasks. (2) `bundles/scheduler.yaml` — 8 actions (list_scheduled_tasks, create_scheduled_task, update_scheduled_task, enable_scheduled_task, disable_scheduled_task, delete_scheduled_task, run_scheduled_task_now, list_runs), 1 destructive (delete_scheduled_task — task definition gone, run history kept via no-cascade FK); integrations: [lib/scheduler.js, lib/postgrest.js]; documents the singleton boot pattern (loads enabled rows at boot, in-memory Map of taskId→cronJob, reload() needed after DB writes), the three command_types (slash dispatched via registry as synthetic owner context; script shell-out with realpath-resolved scripts/scheduled-jobs/ allowlist + shell-metachar refusal; prompt direct spawnSync claude — known bypass of lib/claude.js tracked in audit doc §4.2); the KODA_SESSION_ID=<id> stdout contract for resumability, expects_session=true guardrail, scheduler.task.refused audit + owner DM on allowlist violation, Australia/Melbourne tz; relevant scheduled_tasks columns + scheduled_task_runs history table; anti-patterns: don't pgPost without reload(), don't bypass allowlist, no shell metacharacters in command, don't add prompt-type tasks for security-sensitive flows until invokeClaudeCode migration done, use disable_scheduled_task to pause (preserves row + history), don't manually edit scheduled_task_runs, don't call _execute* methods directly. (3) `bundles/transcription.yaml` — 1 action (transcribe_voice_note), read-only no destructive_actions; integrations: [lib/voice-transcribe.js]; chokepoint_fn: transcribeVoiceNote; documents the deliberate split from the koda-transcribe MCP (this bundle = chat-latency ephemeral small.en for inbound voice notes; MCP = persistent searchable medium model for podcasts/videos), the two-step pipeline (ffmpeg /opt/homebrew/bin/ffmpeg → 16kHz mono WAV with 15s timeout → whisper-cli /opt/homebrew/bin/whisper-cli with 30s timeout), the model bias prompt 'Koda. Wandi. DET22. koda.systems. Klaus.' (whisper otherwise mishears Koda→Coder, Klaus→class, Wandi→Wendy), empty-transcript guard surfacing 'no speech detected' to caller; anti-patterns: don't shell out to whisper-cli/ffmpeg directly (chokepoint owns bin paths, timeout, tmpdir cleanup, bias, empty-transcript guard), don't use for long-form (use the koda-transcribe MCP), don't cache transcripts here, don't pass relative paths, don't switch to medium model for chat latency, don't swallow empty-transcript errors silently. All three bundles parse cleanly through lib/bundles/parser.js (loadBundleFile validates name/version/description/integrations/permissions/destructive_actions cross-check/instructions/actions); commits b9e7be5/62ca72c/e310dc6. Five retrofits remain (P3-P4): cloudflare-deploy, vscode-bridge, web-push, web-control, pdf-generation. |
| 1 May 202612:00 am |
koda-core |
2.78.0 |
EIP Q1 #21 P1 retrofit — 4 bundles shipped (det22, usermgmt, signal, whatsapp). Registry coverage 1 → 5 bundles, 5 → 29 declared actions. (1) `bundles/det22.yaml` — 9 actions, 2 destructive (delete_activity, clear_availability), all wired to `lib/intents.js.executeIntent` with CF Access dual-layer auth (cf-access-det22 cred + X-Koda-Service-Token HMAC) documented; substantial instructions block covers the 5-step dispatch path, 4-tier RBAC, identity-action security verified params (rsvp.on_behalf_of, set/query/clear_availability.user_id), anti-patterns (no direct fetch to det22.koda.systems, no name-based references in verified params, no D1-direct writes). (2) `bundles/usermgmt.yaml` — 4 actions, 2 destructive (update_user, assign_role); assign_role's cascading_role gate documented (requestor's effective role must outrank params.role per identity-action-security.md Layer 3); 4-role RBAC scoped per-app; anti-pattern call-out that lib/usermgmt-client.js is the read-side sync mirror, not the mutation path. (3) `bundles/signal.yaml` — 3 actions on lib/channels/signal.js (send_message, send_to_group, send_chunked); NOT destructive; signal-cli REST API auth model documented (localhost-bound sidecar, no per-call auth); markdown→styled three-phase converter documented; signal-notify.sh:77 curl bypass flagged as the one justified bypass (restart scenarios where the lib isn't loaded). (4) `bundles/whatsapp.yaml` — 8 actions on lib/channels/whatsapp.js (send_message, send_file, send_chunked, send_seen, start_session, get_session_status, get_media_file, fetch_inbound_media); NOT destructive; WAHA dual-layer auth (localhost network + WAHA_API_KEY) documented; server.js:1555-1556 axios.get(.../api/default/groups/...) flagged as a known bypass requiring a future getGroupInfo chokepoint (P3 retrofit); waha-media path-traversal discipline documented; messaging.read_media gate for media-fetch actions. Validated end-to-end: registry parses all 5 bundles cleanly, getActionDef resolves the representative action per bundle, checkBundleGate hits Case A (allowed=true) on a synthetic det22.create_activity intent. P1 audit doc scoreboard refresh: Tier 2 Workers (det22+usermgmt) Reg. AMBER → GREEN (now 4/5); Signal Reg. RED → GREEN (now 2/5); WhatsApp Reg. RED → GREEN (now 2/5). Eight retrofits remain (P2-P4): tasks, scheduler, transcription, cloudflare-deploy, vscode-bridge, web-push, web-control, pdf-generation. |
| 30 Apr 202610:30 pm |
koda-core |
2.77.1 |
EIP audit hygiene + anomaly alert UX. (AMBER-5) `/bundles show` validator: dropped the `i` flag from the regex so `MixedCase` names get a clear 'Invalid bundle name. Bundle names must be lowercase-kebab' error instead of the misleading 'Bundle not found. Try /bundles list' that the case-sensitive registry lookup produced after the case-insensitive validator passed (`lib/commands/bundles.js`). (AMBER-6) `/help` Bundle Router (EIP) section now lists all three subcommands inline (`/bundles list /bundles show <name> /bundles validate`) instead of the bare `/bundles` it carried before — matches the multi-subcommand pattern used elsewhere in /help (`lib/commands/help.js`). Sister-changes (no koda-core source touched): (1) `docs/anomaly-rules.json` — destructive-action-off-hours rule excludes `session_reset` from the destructive-action match (regular UX action, was firing false positives nightly when the owner reset a console session in off-hours); (2) `scripts/scheduled-jobs/anomaly-watchdog.js` — when a rule fires, the watchdog now does a follow-up GET for up to 5 sample rows and appends `• <action> @ HH:MM <app> <channel>` lines to the alert DM, plus `…and N more` if total exceeds sample size, so the owner sees what triggered the rule instead of a bare count; (3) `docs/external-integration-audit.md` refresh — header version 2.68.0 → 2.77.0, §1 scoreboard split Tier 2 row (det22+usermgmt → 3/5 with CF Access service tokens, dashboard → 2/5), added Google Calendar row (5/5), §4.3 narrative refreshed to note calendar is now LIVE end-to-end and pre-flight matcher wiring is shipped, §6 #11b strikethrough since /db proxy was killed in 2.71.0. |
| 30 Apr 202610:00 pm |
koda-core |
2.77.0 |
EIP Q1 #21 pre-flight — Runtime 1 (bundle matcher) is now live across all five `buildSystemPrompt` call sites: `server.js` handleMention path, `lib/api/chat.js` /api/chat/send, `lib/api/messages.js` POST /api/messages, `lib/commands/modify.js` (both /modify <instruction> plan branch and /modify confirm branch), and `lib/commands/work.js` queue task executor. Each site invokes `matchBundles({ userText, bundles: registry.getAllBundles() })`, resolves matched bundle objects via `getBundle(name)`, audit-logs `bundle.matched` (with `match_scores` detail JSON, channel-aware), and passes the resolved array as the final `bundleMatches` argument to `buildSystemPrompt` so matched bundle instructions land in the system prompt. (RED-2) Bundle registry now preloaded at boot in `server.js` next to `loadApps()` — fail-loud on error so an unloaded registry never goes silently undetected. Closes the lazy-load race that previously dropped first-100ms-after-restart intents into Case C. (AMBER-1) `lib/api/messages.js` `buildSystemPrompt` call had wrong signature (`(groupId, req.user)` — the user object was rendering as `groupInstructions` text); now correctly fetches global+group instructions, user permissions, and memories like `lib/api/chat.js` does. (AMBER-4) `lib/intents.js` BUNDLE_REFUSAL_DM_TTL_MS now has an explicit comment block documenting the accept-as-is decision: in-process Map resets on restart by design — informative signal that the underlying loop is still happening; volumetric anomalies covered separately by audit_log watchdog. Verified live: `audit_log` shows `bundle.matched` rows from owner messages during the rolling restarts (channel=signal, calendar bundle scoring 2 from `update_event` action keyword); boot log shows `[boot] bundle registry preloaded count=2`. Per-bundle retrofit (det22, usermgmt, signal, whatsapp, etc.) and hard-fail flip deferred to follow-up sessions per `instructions/eip-q1-21-bundle-retrofit.md`. |
| 30 Apr 20267:30 pm |
koda-core |
2.76.0 |
EIP Q1 #17 close-out — bundle router runtimes 2 & 3 + tooling. (D, M7-D) Always-on `skills/bundle-self-check.md` skill auto-injected into every Claude system prompt by `lib/instructions.js` (loaded once at module init); makes the model self-check that proposed external calls go through a bundle's chokepoint, refuse cleanly when no bundle exists, and never bypass the registry for chat-surface requests (cron is the only path with a bypassToken). (E, M7-E) Runtime 3 dispatch gate shipped in `lib/intents.js.checkBundleGate` — invoked by both `executeIntent` and `executeBatch` BEFORE the outbound fetch. Three cases: bundle present + action present → pass; bundle present + action missing → refuse with `intent.refused` audit row + owner DM (suppressed per-`app.action` for 30 min via in-process Map); bundle absent → refuse if manifest opts in via `bundle_required: true`, else pass through with `intent.bundle_missing` audit row (transitional state until #21 retrofit). Audit writes flow into existing `audit_log` table — chose this over a dedicated `bundle_router_log` table so the anomaly watchdog already covers it. Imports `getBundle` / `getActionDef` from the lazy-loaded `lib/bundles/registry`; gate fail-opens with a logged warning if the registry hiccups so a transient registry issue cannot block legitimate traffic. (F, M7-F) `/bundles list|show|validate` read-only inspector at `lib/commands/bundles.js` (owner-only, hot-reloaded). `list` shows `name (vX.Y.Z) — description (N actions)` plus a separate Errors section for any YAMLs that failed to parse. `show <name>` pretty-prints the bundle (integrations, permissions, destructive marker, action table with chokepoint_fn + perm, first 1000 chars of instructions). `validate` re-runs `registry.refresh()` and reports load counts + added/removed since previous load; survives a deliberately-broken YAML cleanly. `/help` updated to surface `/bundles` under a new owner-only Bundle Router section. (G, M7-G) Migration helper `scripts/bundle-from-app.js <app> [--out path] [--force]` — bootstraps a starter `bundles/<app>.yaml` from an existing `apps/<app>/manifest.koda.json`. Maps name/version/description/destructive_actions, builds an `actions{}` map with a `TODO_set_chokepoint_fn` placeholder per intent (manifests don't carry one), flattens unique `intent.permission`s, scaffolds an instructions skeleton with a TODO_chokepoint_module marker, validates the generated YAML through `lib/bundles/parser.js` before writing, refuses to overwrite without `--force`. Dry-run on det22 produces a 9-action starter (11 TODOs); usermgmt produces a 4-action starter (6 TODOs); both parse cleanly. Closes EIP Q1 #17 (DB task #263); audit doc §4.3 RED → AMBER and remediation list #17 marked done with as-built notes (telemetry deviation: audit_log instead of bundle_router_log; bundles/ at top level, not skills/bundles/). Outstanding: #21 retrofits det22 / usermgmt / calendar to declare bundles so Runtime 3 stops falling into legacy pass-through for them; #20b koda-dashboard CF Access service token migration. |
| 30 Apr 20266:00 pm |
det22 |
1.10.1 |
EIP Q1 #20 deploy — live cutover. authenticateAdmin() now checks claims.common_name against env.EXPECTED_SERVICE_TOKEN_CN (CF Access puts the issuing token's *client_id* in common_name, not the human token name as the brief assumed; brief deviation documented). CF_ACCESS_AUD secret added (was missing — verifyAccessJwt was throwing 1101 on every JWT path). Worker deployed to det22.koda.systems and live-verified: matching token → 200 (admin/health), cross-app token (usermgmt) → 401. |
| 30 Apr 20266:00 pm |
usermgmt |
1.8.1 |
EIP Q1 #20 deploy — live cutover. Same middleware fix as det22 1.10.1 (common_name vs client_id). CF_ACCESS_AUD secret added. Deployed to users.koda.systems and live-verified: matching token → 200 (admin/health), cross-app token (det22) → 401. |
| 30 Apr 20265:00 pm |
koda-core |
2.75.0 |
EIP Q1 #20 (Koda Core caller side) — admin calls to Tier 2 apps now use per-app CF Access service tokens (locked decision #8). New `getCfAccessHeadersForApp(appName)` helper in lib/auth-headers.js reads from credentials domain `cf-access-${appName}` (encrypted as `<client-id>:<client-secret>` written by scripts/cred-set.sh). Falls back to the shared `cloudflare-access-service` token when the per-app cred is missing, so the rollout can land in stages — every existing /admin/* call keeps working until each CF Access app is reconfigured to require its specific service token at the edge. Per-app result is cached; new `clearCfAccessHeadersCache(appName)` invalidates it after a token rotation. lib/sync.js wired through at all four /admin/* call sites: `_directSyncPermissionsToApp` → cf-access-${appName}, `checkAppHealth` → cf-access-${appName}, `syncDashboardEntitlements` → cf-access-koda-dashboard, `collectAuditLogs` → cf-access-${appName}. Companion to the worker-side change in det22 1.10.0 + usermgmt 1.8.0 (admin endpoints now accept either CF Access service-token JWT with matching common_name claim, or the legacy ADMIN_API_KEY bearer for back-compat). Phase 1 (CF Zero Trust dashboard token issuance) and Phase 5 (worker `wrangler secret put APP_NAME` + `wrangler deploy`) blocked on owner. Also adds scripts/cred-set.sh helper (companion to get-cred.js) for storing encrypted credentials from the CLI; documented chokepoint bypass for direct PostgREST access (same justification as get-cred.js). |
| 30 Apr 20265:00 pm |
usermgmt |
1.8.0 |
EIP Q1 #20 (worker-side) — usermgmt /admin/* endpoints now route through new authenticateAdmin() in src/worker/middleware.js. Primary auth: CF Access service-token JWT, validated by the shared apps/_shared/worker/cf-access-jwt.js verifier; the resulting claims must carry a common_name claim equal to `koda-core → ${env.APP_NAME}` (i.e. `koda-core → usermgmt`). A token issued for any other app's CF Access application is refused with a 403 — this is the cross-app admin-spoofing block called out in locked decision #8. Legacy `Authorization: Bearer ${env.ADMIN_API_KEY}` is kept as a back-compat fallback so existing Koda Core sync calls continue to work until CF Access is configured to require the service token at the edge for /admin/* (Phase 1 + 5 of the instruction, blocked on owner). Bundle now declares `cf_access: { service_token: cf-access-usermgmt, common_name: koda-core → usermgmt }` for chokepoint discoverability. /api/* user flow is untouched — only /admin/* moved. |
| 30 Apr 20265:00 pm |
det22 |
1.10.0 |
EIP Q1 #20 (worker-side) — det22 /admin/* endpoints now route through new authenticateAdmin() in src/worker/middleware.js. Primary auth: CF Access service-token JWT, validated by the shared apps/_shared/worker/cf-access-jwt.js verifier; the resulting claims must carry a common_name claim equal to `koda-core → ${env.APP_NAME}` (i.e. `koda-core → det22`). A token issued for any other app's CF Access application is refused with a 403 — cross-app admin spoofing blocked. Legacy `Authorization: Bearer ${env.ADMIN_API_KEY}` is kept as a back-compat fallback so existing Koda Core sync calls keep working until CF Access enforces the service token at the edge for /admin/* (Phase 1 + 5 of the instruction, blocked on owner). Bundle now declares `cf_access: { service_token: cf-access-det22, common_name: koda-core → det22 }`. /api/* user flow is untouched. |
| 30 Apr 20264:00 pm |
keysvc |
0.1.0current |
EIP Q1 #19 — koda-keysvc readiness artifacts. New keysvc/ directory ships a tiny single-purpose Node sidecar (stdlib only) that owns the credential encryption key and exposes encrypt / decrypt / ping over a Unix socket (default /var/run/koda-keysvc.sock, perm 0660 owner kodasvc:koda). Wire format mirrors lib/crypto.js exactly (<iv-hex>:<tag-hex>:<ct-hex>, optional ENC: prefix), so every existing encrypted credential continues to decrypt unchanged after cutover; smoke-tested both directions against the live .secrets/credential_key. Audit log at /var/lib/koda-keysvc/keysvc.log records every op (status, requestor, op). Rate limiter caps blast radius at 100 ops/sec (configurable via KEYSVC_MAX_OPS_PER_SEC). Idempotent setup-admin.sh creates the kodasvc user, /var/lib/koda-keysvc, copies the existing keyfile byte-for-byte (no key rotation), and loads com.koda.keysvc.plist into /Library/LaunchDaemons/. Ships in readiness state only — Phase 1 (sudo install) is BLOCKED on Klaus running keysvc/setup-admin.sh; Phase 3 (cutover: modify lib/crypto.js to call keysvc + restart Koda Core) is intentionally a separate follow-up so it lands AFTER keysvc is verified live. Inline eslint-disable on require('crypto') in keysvc.js is the deliberate Layer-1 exception — keysvc IS the new chokepoint owner of the AES-GCM key in a separate trust domain. |
| 30 Apr 20263:30 pm |
koda-core |
2.74.0 |
EIP Q1 #18 — Google Calendar reference bundle (first end-to-end exercise of the EIP Layer 3 pipeline). New lib/calendar.js chokepoint owns the googleapis dep (v171.4.0); lazy JWT auth via google.auth.JWT using the service-account JSON key loaded from cred store domain google-calendar-service-account-json (decrypted via lib/crypto), with concurrent-call coalescing on the auth handshake. Five public actions: listCalendars, listEvents, createEvent, updateEvent, deleteEvent — each emits an audit_log row via lib/audit.logAction (action prefix calendar.*, channel=autonomous, three-actor model with directedBy=requestor). Service-account-with-calendar-sharing model — works on personal Gmail (no Workspace, no DWD); Klaus shares each calendar with the service-account email manually. New bundles/calendar.yaml (parses clean via lib/bundles/parser): 5 actions, 2 permissions (calendar.read/write), delete_event marked destructive, full anti-pattern docs in instructions block (do-not lists for googleapis-direct, gcalcli, ID caching, bulk-delete-without-confirm). docs/chokepoints.json updated — lib/calendar.js no longer status: planned, managed_credentials corrected to google-calendar-service-account-json, rationale points at the new bundle. ESLint chokepoint ban now active for googleapis (verified: require('googleapis') outside lib/calendar.js fails lint). Phases 1+2 (GCP service-account setup + calendar sharing + cred upload) are owner actions and remain blocked pending Klaus' setup; the chokepoint surfaces a precise error when the cred is missing, pointing back to the instruction file. |
| 30 Apr 20263:00 pm |
koda-core |
2.71.0 |
EIP Week-2 #11 (audit H5) — `/db` PostgREST proxy deleted from server.js. The localhost-only passthrough was unused dead code: console PWA already routes through named `/api/*` endpoints (apps/console/js/api.js), no scripts called it, and the only test was the proxy's own auth-check assertion. All internal DB access now flows exclusively through lib/postgrest.js (cache + retry + audit). Updated tests/security/server-endpoints.test.js to assert the route is absent (`grep app\.(get|post|...)\(['"]/db['"]` returns nothing). Audit doc strikethrough markers added for H5 + §4.2 bypass row + §4.4 logging gap. |
| 30 Apr 20262:15 pm |
koda-core |
2.73.0 |
EIP Week-2 #13 (audit M5 / Layer 4 alerting) — anomaly watchdog cron + 4 starter rules over audit_log. New docs/anomaly-rules.json declares rules as (label, lookback_minutes, postgrest_query, threshold, severity, optional active_hours) tuples; queries adapted to the real audit_log schema (timestamp / app_name / outcome — not the strawman ts/integration/success/destructive columns from the instruction draft). New scripts/anomaly-watchdog.js: reads rules JSON each tick, HEAD-queries PostgREST with Prefer: count=exact, parses Content-Range total, fires DM via signal-notify.sh on threshold breach, suppresses re-alerts for 30min via tmp/anomaly-state.json (rule state cleared once count drops below threshold so next firing alerts immediately). 4 starter rules: auth-failure-burst (5+ in 10min), scheduler-failures (3+ in 1h), destructive-action-off-hours (1+ in 30min, 22:00-06:00 only, action ilike *delete*/*destroy*/*drop*/*wipe*/*reset*), audit-volume-spike (500+ in 5min). Two chokepoint bypasses documented in docs/chokepoints.json (postgrest + signal — cron must work when Koda Core is down). Registered in scheduled_tasks (id=6, */5 * * * *) and live scheduler picked it up via API PATCH (next_run_at populated). Smoke-tested via --dry-run: all 4 rules executed cleanly against live PostgREST, state file written. |
| 30 Apr 20262:05 pm |
koda-core |
2.72.0 |
EIP Week-2 #12 (audit M3) — integration registry + `/status integrations` + real chokepoint linter. (1) docs/chokepoints.json schema extended with health_endpoint, expected_health_status, related_scripts, managed_credentials per chokepoint, plus a top-level mcp_servers section enumerating koda-transcribe / stitch (stdio MCPs from ~/.claude.json) and playwright-mcp (long-running launchd plist). (2) New lib/integrations.js exposes loadRegistry / listIntegrations / listMcpServers / checkHealth / checkAllHealth (parallel fetch with per-call AbortController timeout, default 3s; mtime-aware cache so hot edits to chokepoints.json take effect without restart). (3) lib/commands/status.js adds an `integrations` subcommand that runs checkAllHealth in parallel and renders a tabular response with healthy/degraded/down/unknown icons plus the declared MCP server list. (4) scripts/lint-chokepoints.js rewritten from stub to a real Node-native walker (no rg dependency): scans .js/.ts/.mjs/.cjs/.sh files for owns_urls (string match) and owns_commands (subprocess-call heuristic — only flags a quoted command appearing as the first argument to spawn/spawnSync/exec/execSync/execFile* calls, plus a shell-syntax heuristic for .sh files); skips node_modules, .git, .venv, vendored skill repos under skills/_repos and skills/excalidraw-diagram/references, instructions/archive, tests, generated site/. (5) 30 legitimate bypasses documented in chokepoints.json (lib/config.js declarations, scripts that run when Koda Core is down, server.js bootstrap constants, and the WAHA URL-string collision). Linter runs clean: 0 violations across 207 scanned files. |
| 30 Apr 20261:30 pm |
koda-core |
2.71.1 |
Two work-auto resume fixes. (1) server.js /internal/work-continue endpoint now treats groupId === OWNER_UUID as an owner DM (leaves dataMessage.groupInfo null) instead of forcing groupInfo and getting silently dropped by the activation gate — mirrors how /internal/plan-continue already handles this case. (2) scripts/self-modify.sh confirmation/auto-resume logic restructured: confirmation message is picked once from any source (CONFIRM_MSG / SM_CONFIRM_MSG / temp file / generic), then the work-auto state file is checked unconditionally, with a 72h staleness threshold. Fresh queues auto-resume via /internal/work-continue with the queue notice appended to the chosen message. Stale queues prompt the owner instead of resuming, on the assumption that 72h+ idle means the queue likely completed and cleanup was missed. Also fixes .husky/pre-commit hook with --no-warn-ignored so staged tests/** files don't trip the eslint 'file ignored' meta-warning (preserves --max-warnings=0 chokepoint enforcement; only suppresses the meta-warning class). |
| 30 Apr 202612:15 pm |
koda-core |
2.70.0 |
EIP Week-1 #8 (H4) — WAHA media reads moved behind the WhatsApp channel chokepoint. Added getMediaFile()/fetchInboundMedia() to lib/channels/whatsapp.js (owns WAHA_MEDIA_ROOT, /api/files URL parser, localhost:3000→host rewrite, retry loop, /messages/{id}/download fallback). server.js webhook handler replaced ~70 lines of inline fs.readFileSync/axios logic with one channels.whatsapp.fetchInboundMedia(payload) call — server.js no longer references waha-media/ or WAHA storage layout. |
| 30 Apr 20268:48 am |
det22 |
1.9.0 |
EIP Week-1 #5 (C1) — proper CF Access JWT signature verification. Worker middleware no longer hand-decodes the JWT body via atob(); instead routes through new shared chokepoint apps/_shared/worker/cf-access-jwt.js which uses jose.jwtVerify against a 1h-cached remote JWKS (createRemoteJWKSet) and validates issuer (https://koda-systems.cloudflareaccess.com) + per-app audience (CF_ACCESS_AUD secret). Closes header-spoofing attack class — even if the worker URL becomes reachable directly, fabricated JWTs are rejected on signature mismatch. |
| 30 Apr 20268:48 am |
usermgmt |
1.7.0 |
EIP Week-1 #5 (C1) — proper CF Access JWT signature verification via shared apps/_shared/worker/cf-access-jwt.js chokepoint. Removed inline atob() body-only decoding from worker middleware; now full JWKS signature + issuer + audience validation via jose. |
| 30 Apr 20268:48 am |
koda-dashboard |
1.3.0 |
EIP Week-1 #5 (C1) — proper CF Access JWT signature verification via shared apps/_shared/worker/cf-access-jwt.js chokepoint. Replaced three call sites (middleware authenticate, serveDashboard, proxyProfile) that previously hand-decoded the JWT body with atob() — all now go through verifyAccessJwt() which performs full JWKS signature + issuer + audience validation via jose. |
| 30 Apr 20268:45 am |
koda-core |
2.69.0 |
EIP Day-1 #3 — ESLint + husky precommit hook for chokepoint enforcement (External Integration Pattern Layer 2). New eslint.config.js (flat config) reads docs/chokepoints.json and bans direct require()/import of owned modules outside the chokepoint owner via no-restricted-imports + no-restricted-syntax (covers CommonJS). New .husky/pre-commit runs `eslint --max-warnings=0` on staged JS files and runs scripts/lint-chokepoints.js stub (URL/command checker — fleshed out in EIP Week-2 #12). Initial known violations annotated with eslint-disable comments referencing fix-tasks (most are non-credential crypto uses; AES-GCM bypasses point to EIP Week-1 #7). Hook tested with deliberate violation. Added npm scripts lint, lint:fix, lint:chokepoints. Devdeps: eslint@^9, husky@^9, lint-staged@^15. |
| 28 Apr 20269:33 pm |
koda-core |
2.68.0 |
/work auto now auto-resumes across bridge restarts. Closes the gap where a restart mid-queue dropped you out and required a manual /work start. New /internal/work-continue endpoint (localhost-only, mirrors /internal/plan-continue) reads the group-scoped tmp/koda-work-auto-{groupId}.json state file and re-fires /work auto via a synthetic envelope through processMessage. scripts/self-modify.sh's verify-script branch that previously printed 'Send /work start to continue' now extracts the groupId from the state file, sends a 'Auto-resuming in 3s...' notification, and POSTs to the new endpoint. Falls back to the manual-resume notice on HTTP failure or missing groupId. |
| 28 Apr 20269:30 pm |
koda-core |
2.67.0 |
Group Listening Mode — opt-in ambient context backlog for @-mentions. New `/listening` command (admin+ to mutate, anyone to view) toggles per-group with `on`/`off`/`<N>`/`status`. When enabled, the last N messages OR last T minutes (whichever fewer, default N=25 / T=120m, hard cap 200) are fetched, deduped against the current invocation, and prepended as a `<conversation_context>` block in the system prompt. Speakers are resolved to display names (Koda's own = 'Koda', unregistered = last 4 phone digits). Attachments rendered as `[image]` / `[voice]` / `[file: name]` / `[sticker]` placeholders. Quote-replies trigger a depth-5 chain walk that surfaces older messages even when outside the time window. Privacy gate: `groups.listening_enabled_at` ensures pre-enable messages never surface (re-enable resets the gate). Anti-injection framing instructs Claude to use the block as background only and act exclusively on the final message. Schema: `groups.listening_n`, `groups.listening_t_minutes`, `groups.listening_enabled_at`. Implementation: `lib/context-backlog.js` (reader/dedupe/formatter), `lib/commands/listening.js` (command), `server.js` wiring before invokeClaudeCode. |
| 28 Apr 20269:02 pm |
koda-core |
2.66.0 |
/work command now surfaces standalone instruction files (those not part of any *-queue.md) alongside queue-managed tasks. Each standalone file with a task-style metadata header (`> **Depends on**:` or `> **Modifies**:`) appears as a virtual single-task queue in /work status, /work list, /work next, and is executable via /work start. On successful completion the instruction file is auto-archived to instructions/archive/, matching the queue-completion convention. Closes the gap where single-task work was invisible to /work. |
| 28 Apr 20268:12 pm |
koda-core |
2.65.1 |
Fix: group-removal deactivation now checks Koda's actual membership, not just group-ID presence in the upstream roster. signal-cli keeps groups in its local cache after you're kicked (only the members array updates), so the previous logic never fired for Signal kicks. Now matches config.SIGNAL_PHONE against g.members for Signal and config.WHATSAPP_BOT_NUMBER against g.participants[].phoneNumber for WhatsApp. Falls back to ID-presence for older WAHA builds without participants data. |
| 28 Apr 20267:59 pm |
koda-core |
2.65.0 |
Owner-only /leave command with two-step confirmation for Koda to leave Signal or WhatsApp groups (channel-aware: signal-cli quit endpoint or WAHA leave endpoint). Plus auto-deactivation: syncGroupNames now polls both signal-cli and WAHA every 15 min and marks any koda_active=true group missing from its channel's roster as inactive, audit-logs the event, and DMs the owner. False-positive guard: deactivation only fires on a successful upstream fetch, so API errors/reconnect blips don't cause false positives. |
| 28 Apr 20267:05 am |
koda-core |
2.64.0 |
Group member roster injection + three-layer identity-action security. Group chats now get a Signal-sourced member roster in the system prompt (awareness only). Three code-enforced gates fire before any koda-intent dispatches: Layer 2 verified-target (manifest params.verified must come from @-mention or literally-typed UUID/phone), Layer 3a requestor permission (enforcePermission, fail-closed), Layer 3b cascading role (canManageRole) for role grants. Manifest schema additions: params.verified[], permission, cascading_role. Applied to usermgmt (update_user, assign_role) and det22 (rsvp, set/query/clear_availability). Full design: docs/identity-action-security.md. |
| 27 Apr 20268:30 pm |
det22 |
1.8.0 |
Admin panel now opens as an embedded modal (iframe to users.koda.systems?app=det22&embed=true) instead of a new tab — user stays in DET22 context. Added shared AdminModal component in apps/_shared/. Source task #243. |
| 27 Apr 20268:30 pm |
usermgmt |
1.6.0 |
Embed mode (?embed=true): hides header/tabs/profile, transparent background, posts events to parent on role-changed/user-invited/user-deleted/close. CSP frame-ancestors header allows iframing from *.koda.systems. Enables embedding inside DET22 and other T2 apps. Source task #243. |
| 27 Apr 20266:15 pm |
koda-core |
2.63.1 |
Voice-note transcription fidelity bump: base.en → small.en (~30% lower WER on conversational English) and added initial prompt biasing decoder toward Koda proper nouns (Koda, Wandi, DET22, koda.systems, Klaus). Latency unchanged for short clips. |
| 27 Apr 20266:05 pm |
koda-core |
2.63.0 |
Signal voice-note ingestion (DM only): single audio attachment → whisper.cpp base.en → transcript injected into chat pipeline. Fully offline, ~1.5s for short clips. Heavy koda-transcribe MCP untouched. Source task #275. |
| 27 Apr 202610:30 am |
koda-console |
1.37.0 |
In-app notification badges: red badge dots on Tasks (new tasks), Chat (unread), and More tab (system events). Badges on hamburger menu items and quick-switch dropdown. localStorage-based last-seen tracking (#268). |
| 27 Apr 202610:00 am |
koda-console |
1.36.1 |
Post-sprint fixes: iOS auto-zoom prevention, per-session draft persistence, chat race condition fixes (abort vs error, session-scoped typing indicator), SW cache bump. |
| 27 Apr 202610:00 am |
koda-core |
2.61.1 |
Wire logSystemEvent() into startup/shutdown hooks, store sessionId in activeClaudeSessions, return sessionId from /api/chat/active. |
| 27 Apr 20269:36 am |
koda-console |
1.38.0 |
Per-user per-channel model selector: tappable model pill on dashboard opens bottom sheet with all available models (versioned, with multiplier badges). Selection persisted server-side per user per channel. |
| 27 Apr 20269:36 am |
koda-core |
2.62.0 |
Version-level MODEL_REGISTRY with per-model multipliers. Per-user per-channel model preferences (user_model_preferences table). refreshModels merges CLI + registry. API returns versioned model list with user preference. |
| 26 Apr 20262:30 pm |
koda-console |
1.36.0 |
Console PWA sprint: Apps management section (view, rename, suspend/resume), System Messages panel with type filters, new menu items in More tab (#233, #268, #272). |
| 26 Apr 20262:30 pm |
koda-core |
2.61.0 |
PATCH /api/apps/:name endpoint, /app rename/suspend/resume commands, system_events table + GET/DELETE API, route mounting (#232, #233, #272). |
| 26 Apr 202611:38 am |
koda-core |
2.60.3 |
Opus 4.7 migration: fix Opus pricing in MODEL_INFO ($5/$25 MTok, 1.67x multiplier), update CLI to 2.1.112, update vscode-orchestrator skill references. |
| 26 Apr 20269:33 am |
koda-core |
2.60.2 |
Add watchdog health checker (scripts/watchdog.js) with dual-channel alerting (Signal + Resend email). Checks Claude CLI auth, disk space, service ports, Docker containers, and image updates. Scheduled every 15 min (#73). |
| 26 Apr 20268:40 am |
koda-console |
1.35.2 |
Restore typing indicator when navigating back to an active chat — polls /api/chat/active and shows 'Claude is typing...' until the response completes. |
| 26 Apr 20268:40 am |
koda-core |
2.60.2 |
Add GET /api/chat/active endpoint to check if Claude is currently processing a response. Also fix missing planFilePath import in /continue command. |
| 25 Apr 202610:30 pm |
koda-core |
2.60.1 |
Fix PWA chat auto-naming bug: scope session-id backfill to current request only, preventing old orphaned messages from contaminating new session titles (#273). |
| 25 Apr 202611:52 am |
koda-core |
2.60.0 |
Persist message queue across restarts: save queued messages to disk on shutdown, restore and re-process on startup. Also fixes session loss during intentional restarts by draining active Claude sessions before exit. |
| 24 Apr 20262:00 pm |
deploy-watch |
1.1.1 |
Code audit fixes: pinned Docker images with SHA digests, added healthchecks, fixed subshell variable scoping in fswatch loop, atomic lockfile for prune-logs, self-modify.sh mkdir lock. |
| 24 Apr 20262:00 pm |
koda-console |
1.35.1 |
Code audit fixes: pinned Tailwind CDN to v3.4.1, restored viewport accessibility zoom, Alpine x-for unique keys. |
| 24 Apr 20262:00 pm |
koda-core |
2.59.0 |
Codebase audit: ~80 fixes across security (path traversal, auth, injection), performance (sync I/O, memory leaks, N+1 queries), race conditions (lockfiles, concurrency), API hardening (ownership checks, role escalation, input validation), and code quality (shared modules, deduplication). |
| 24 Apr 20261:20 pm |
koda-core |
2.59.1 |
Fix usage scraper: switch to playwright-extra with stealth plugin (non-headless) to bypass Cloudflare bot detection on claude.ai. |
| 23 Apr 202612:00 pm |
koda-core |
2.58.0 |
Multi-channel abstraction: conversational paths now channel-agnostic, control-plane notifications configurable (ADR-020). |
| 22 Apr 20269:04 pm |
koda-console |
1.35.0 |
Stale-while-revalidate caching for dashboard and tasks tabs — app opens instantly with cached data, refreshes in background (#269). |
| 22 Apr 20269:00 pm |
koda-console |
1.34.1 |
Chat timeout errors now show as visible messages in the conversation instead of brief toasts. |
| 22 Apr 20269:00 pm |
koda-core |
2.57.1 |
Chat SSE error events now include session ID for client recovery after timeout. |
| 22 Apr 20267:19 pm |
koda-core |
2.57.0 |
Add /btw slash command — side-channel questions that bypass the message queue, with forked session context via --resume --fork-session (#192). |
| 22 Apr 20264:20 pm |
koda-dashboard |
1.2.0 |
Profile menu now shows editable First Name / Last Name fields (shared ProfileMenu component extended). Fixed null names from broken onboarding proxy. |
| 22 Apr 20262:30 pm |
koda-dashboard |
1.1.0 |
Onboarding flow with name confirmation, guided tour, profile menu, group chat indicator on tiles, URL renamed to dash.koda.systems (#206). |
| 22 Apr 20262:30 pm |
usermgmt |
1.5.0 |
Self-service profile: first_name/last_name columns, GET /api/me returns names, PATCH /api/me for self-service name updates (#206). |
| 22 Apr 20262:30 pm |
koda-core |
2.56.0 |
Dashboard entitlements sync now includes has_group_chat from workspace_surfaces (#206). |
| 22 Apr 202610:35 am |
usermgmt |
1.4.0 |
Email invite notifications: auto-send welcome email on user creation, email-linked notification on proxy email linking, resend invite endpoint + UI badge (#261). |
| 22 Apr 20269:44 am |
det22 |
1.7.2 |
Replace freeform category input with pill selector (Range/CAPDEV/OAI/Skills) (#267). |
| 21 Apr 202610:50 pm |
koda-core |
2.55.0 |
Add /api/mcp-status endpoint for MCP server health (#265). |
| 21 Apr 202610:50 pm |
koda-console |
1.34.0 |
Add MCP server status pills to dashboard card (#265). |
| 21 Apr 202610:50 pm |
dashboard-generator |
1.11.0current |
Add MCP servers section to koda.systems status page (#247). |
| 21 Apr 202610:21 pm |
dashboard-generator |
1.10.1 |
Add component versions section to the build page (#94). |
| 21 Apr 202610:12 pm |
usermgmt |
1.3.0 |
Auto-sync CF Access policy on user create/delete; order role dropdown most→least privileged (#34, #250). |
| 21 Apr 20269:48 pm |
koda-console |
1.33.1 |
Fix PWA chat lifecycle persistence: save state on visibilitychange/pagehide, persist and restore draft text (#210, #235). |
| 21 Apr 20265:14 pm |
usermgmt |
1.2.0 |
Show app display_name instead of slug in role picker and user detail view (#259). |
| 21 Apr 20265:14 pm |
koda-console |
1.33.0 |
Pre-select app scope in create-task expanded form when filter pill is active (#253). |
| 21 Apr 20265:14 pm |
koda-core |
2.54.0 |
Fix HOT_RELOADABLE set (remove destructured modules) and await /model DB writes (#209, #217). |
| 21 Apr 20264:40 pm |
koda-core |
2.53.0 |
Save scheduled task sessions to session_history so they appear in /resume. |
| 20 Apr 20269:00 pm |
det22 |
1.7.1 |
Fix cell tap interaction: click for single-tap edit, pointer events only for drag-paint, pointer-events:none on child elements. |
| 20 Apr 20268:15 pm |
det22 |
1.7.0 |
Availability UI: toolbar restructure (edit top-left, view/range swap on right), sticky month headers, readable aggregate counts. |
| 20 Apr 20267:58 pm |
koda-core |
2.52.1 |
Inject sender identity (created_by + directed_by) into intent params — Koda acts as EA, directing user shown as creator. |
| 20 Apr 20267:55 pm |
det22 |
1.6.2 |
Resolve created_by email to user_id for directed-by attribution on service-token POSTs. |
| 20 Apr 20267:15 pm |
det22 |
1.6.1 |
Fix POST crash on service token auth: null created_by fallback to email/koda label. |
| 20 Apr 20267:05 pm |
koda-core |
2.52.0 |
Fix intent pipeline: CF Access + service token auth from credential store, auto-execute high-confidence (>=0.9) intents as batch without per-item confirmation, multi-pending intent support. |
| 20 Apr 20266:00 pm |
det22 |
1.6.0 |
Edit mode toggle for availability (off=scroll freely, on=drag-paint). Scroll overshoot fix with 300ms cooldown. Admin icon replaced with settings cog. Tasks #244, #249, #251. |
| 20 Apr 20265:00 pm |
det22 |
1.5.0 |
RSVP counts centered under status pill. Light/dark theme toggle with system preference detection. Fix tile gap. Desktop responsive layout with 2-col grid. |
| 20 Apr 20264:00 pm |
det22 |
1.4.0 |
Card layout: meta chips + RSVP summary on same row. Calendar range picker replaces dual date inputs. Remove summary tile gap. Usermgmt DET22 naming fix. |
| 20 Apr 20264:00 pm |
usermgmt |
1.1.1 |
Fix app slug placeholder to show DET22 (uppercase) consistently |
| 20 Apr 20269:03 am |
koda-core |
2.51.0 |
Enforce session ID capture for scheduled scripts: scripts emit KODA_SESSION_ID, scheduler parses and warns if missing (expects_session flag) |
| 19 Apr 20268:30 pm |
det22 |
1.3.0 |
Card layout overhaul: RSVP summary on right (1/0/0 format), condensed same-month dates, notes+actions bottom row, person icon avatar, drag handle touch areas, reduced header spacing |
| 19 Apr 20267:50 pm |
det22 |
1.2.0 |
RSVP toggle: tapping already-selected status removes RSVP. Date fields stack vertically on mobile. Playwright test helper (pw-browse.js). |
| 19 Apr 20267:20 pm |
det22 |
1.1.3 |
Fix date fields overflowing modal on iOS — add min-width:0 to flex date inputs, prevent horizontal scroll |
| 19 Apr 20267:00 pm |
det22 |
1.1.2 |
Fix User Management link URL from usermgmt.koda.systems to users.koda.systems in profile menu |
| 19 Apr 20265:30 pm |
koda-core |
2.50.0 |
Integrate User Management app: usermgmt-client.js, /app sync delegates to usermgmt API with Postgres fallback, config vars for USERMGMT_API_URL/SERVICE_TOKEN |
| 19 Apr 20265:15 pm |
usermgmt |
1.1.0 |
Derived access model: app admins get implicit scoped access without explicit role. Post-create role assignment flow. Proxy user checkbox. Audit log endpoint. Filtered app picker. |
| 19 Apr 20262:00 pm |
usermgmt |
0.1.0 |
Scaffold User Management app: D1 database, wrangler config, worker boilerplate, frontend shell, shared components |
| 19 Apr 20261:08 pm |
usermgmt |
1.0.0 |
First production release: deployed to users.koda.systems, D1 schema, user CRUD, role management, sync push to Tier 2 apps with CF Access bypass, app-aware theming, PWA frontend |
| 19 Apr 202610:58 am |
koda-core |
2.49.1 |
/work next stashes queue context so /work start and /work auto continue without re-specifying the queue |
| 19 Apr 202610:37 am |
det22 |
1.1.1 |
Desktop availability calendar: constrain grid to 600px max-width, centre layout, improve spacing |
| 19 Apr 202610:30 am |
det22 |
1.1.0 |
Frontend overhaul: tappable summary tiles as filters, search bar, DET22 header with view subtitle, profile sheet slide animation + name edit tick/cross, contextual return-to-today, FAB sizing |
| 19 Apr 20269:33 am |
koda-core |
2.49.0 |
Scheduled tasks capture session ID from prompt-type runs and include resume command in notifications |
| 18 Apr 20266:20 pm |
det22 |
1.0.1 |
Rename app from det22-activity-tracker to det22 — directory, manifest, DB, wrangler, all references |
| 18 Apr 20265:07 pm |
koda-core |
2.48.0 |
Add /wandi slash command (pause/unpause/status), fix 3-step unpause flow, document ADR-019 (script wrappers as commands not apps) |
| 18 Apr 20262:10 pm |
koda-console |
1.32.0 |
Dynamic app list in task forms — fetches registered apps from koda_apps API instead of hardcoded Console-only dropdown |
| 18 Apr 202612:00 pm |
det22 |
1.0.0 |
Add profile menu, remove System tab (system info moved to profile sheet), clean up header UI |
| 18 Apr 202612:00 pm |
koda-console |
1.31.0 |
Add reusable profile menu component (avatar, popover, profile sheet) replacing plain text user display in header |
| 18 Apr 20269:30 am |
koda-console |
1.30.0 |
Pull-to-refresh: Facebook-style circular arrow indicator, disabled in chat conversation threads, spins in place until refresh completes. SW version now tracks console version. |
| 17 Apr 202611:26 am |
koda-console |
1.29.1 |
Fix cross-channel session contamination: moved session resume from separate pre-flight API call to inline metadata passed with send request. |
| 17 Apr 202611:26 am |
koda-core |
2.47.1 |
Fix cross-channel session contamination: send endpoint handles session resume inline, waits for active Claude invocations before swapping sessions. Prevents PWA resume from disrupting in-flight Signal conversations. |
| 17 Apr 202611:18 am |
koda-core |
2.47.0 |
Capability gates for transcription and document generation: added transcribe + generate_document to grantable capabilities, system prompt now explicitly enforces capability checks on all gated features. Published role-capability-matrix.html to koda.systems. |
| 17 Apr 202610:50 am |
koda-core |
2.46.2 |
Rich role-assignment confirmation: /user role and /user grole now show assigned role, active LLM, permissions summary, and full workspace access list (#230). |
| 17 Apr 202610:36 am |
koda-console |
1.29.0 |
PWA bug batch: fix stream session contamination (#228), streamed messages on re-entry (#212), send lock scoped to session (#213), blank push notifications (#215), newline preservation (#218), input re-fill on resume (#226), autoGrow scroll jump (#229), suppress push when foreground (#211). |
| 17 Apr 202610:36 am |
koda-core |
2.46.1 |
Sanitize push notification preview text: strip markdown formatting, handle blank/whitespace content (#215). |
| 17 Apr 202610:02 am |
koda-console |
1.28.0 |
Create-task form now supports attachments: stage files before task creation, uploaded automatically after the task is created (task #227). |
| 16 Apr 202611:10 am |
koda-console |
1.27.0 |
Task attachment UI: upload files to tasks, view thumbnails, delete attachments from task edit modal. |
| 16 Apr 202611:10 am |
koda-core |
2.46.0 |
Task attachments feature: task_attachments table, REST API endpoints (upload/list/serve/delete), Signal+WhatsApp attach-to-task detection with confirmation messages. |
| 16 Apr 202610:50 am |
vscode-bridge |
0.2.5 |
Add timestamps to all log output, add /ready deep health check endpoint (verifies Copilot can actually respond, not just that HTTP server is up). New vscode-orchestrator skill for delegating build work. |
| 15 Apr 202612:37 pm |
koda-core |
2.45.1 |
Fix WhatsApp inbound media attachments: add WHATSAPP_FILES_MIMETYPES=* for WAHA auto-download, fallback to /api/default/messages/{id}/download endpoint when media.url 404s, debug logging for media objects. |
| 15 Apr 20269:25 am |
koda-core |
2.45.0 |
Multi-mention guard: reject commands with >1 @mentioned user (returns clear error instead of guessing). Updated Group Chat Guide and RBAC diagram for multi-channel (Signal + WhatsApp), documented one-workspace-one-channel design decision. |
| 14 Apr 20266:30 pm |
dashboard-generator |
1.10.0 |
Refactor Flow page into Reference Docs with category tabs (Architecture/Reference/Process). Architecture tab keeps version selector; Reference and Process tabs show current-only content. Consolidated task lifecycle into Process tab. Updated nav links across all pages. |
| 14 Apr 20265:50 pm |
koda-core |
2.44.0 |
Two-layer RBAC: workspace-scoped roles with global floor. Effective role = max(global, workspace). Context-aware /user commands with @mention support in groups, explicit IDs in DMs. New: /user grole (global role), /user remove, /user workspaces, auto-revoke on group leave, /activate auto-creates workspaces, workspace-scoped capabilities. Replaced checkBinaryGate with getEffectiveRole. |
| 14 Apr 202612:20 pm |
koda-core |
2.43.0 |
WhatsApp channel integration via WAHA (NOWEB engine). New lib/channels/ abstraction: unified send() router for Signal and WhatsApp, webhook endpoint for inbound WhatsApp messages, channel-aware processMessage pipeline, session auto-start, message formatting per-channel, LID (Linked ID) addressing support for WhatsApp Web protocol. Also: group activation gate (Koda inert in unactivated groups). |
| 13 Apr 20266:45 pm |
dashboard-generator |
1.9.1 |
Standardise nav link positioning across all pages (separate nav-links from page-meta divs). Add ambient corner orbs to all pages via sharedCSS() using per-page accent colours. New koda-site-design-system skill. |
| 13 Apr 20266:15 pm |
koda-console |
1.26.1 |
Standardise Chat session header to match section label pattern (text-xs uppercase). New pwa-design-system skill documenting typography scale, colour palette, and component patterns. |
| 13 Apr 202610:36 am |
koda-core |
2.42.0 |
Web Push notifications for Console PWA: VAPID key management, push_subscriptions table, /api/push endpoints (subscribe/unsubscribe/test/status), service worker push+notificationclick handlers, client subscription flow with iOS re-subscribe, Settings toggle UI. Pushes only when user has no active SSE/long-poll connection. |
| 12 Apr 20269:20 pm |
dashboard-generator |
1.9.0 |
v3-10 documentation: version tabs (v1/v2/v3) on build page, v3.0 checklist tracking from checklist-v3.json, architecture diagrams (4 HTML→PNG via Playwright), v3 install notes, updated landing page description. |
| 12 Apr 20268:05 pm |
koda-console |
1.26.0 |
Audit log view in More tab with filters (app, actor, channel, date range), actor-type badges (user-direct/koda-directed/koda-autonomous), and detail JSON expansion. |
| 12 Apr 20268:05 pm |
koda-core |
2.41.0 |
Three-actor audit trail: lib/audit.js module, audit_log schema extensions (directed_by, channel, app_name), Tier 2 audit collection in lib/sync.js, /app audit command, GET /api/audit endpoint. |
| 12 Apr 20267:45 pm |
koda-core |
2.40.0 |
v3.0 Tier 2 app framework: updated scaffold-app.sh with admin API/permissions/audit templates, deploy-app.sh with secrets/sync/health verification, lib/sync.js for permission push and health monitoring, /app command handler, daily reconciliation script. |
| 12 Apr 20267:06 pm |
dashboard-generator |
1.8.0 |
Add Open Dashboard CTA button to koda.systems landing page, linking to dashboard.koda.systems. |
| 12 Apr 20266:35 pm |
koda-core |
2.39.7 |
Fix work queue post-restart check race: also match 'interrupted' status since server.js startup changes auto-queue state before verify script runs. |
| 12 Apr 20266:05 pm |
koda-core |
2.39.6 |
Work queue restart UX: post-restart message now detects active work queues and tells owner to send /work start to continue, instead of generic 'restarted and verified'. |
| 12 Apr 20265:40 pm |
koda-core |
2.39.5 |
Work queue resume fix: findNextTask() now prioritises in_progress tasks over pending, so interrupted queue items are resumed after restart instead of skipped. |
| 12 Apr 20265:25 pm |
koda-core |
2.39.4 |
Work queue progress tracking: /work auto sessions now mark completed steps with COMPLETED markers and tick verification checkboxes in instruction files, enabling clean resume after restarts. |
| 12 Apr 20265:05 pm |
koda-core |
2.39.3 |
Startup cleanup: auto-archive completed plan files to prevent stale plans confusing new sessions; detect interrupted /work auto runs and notify owner to resume. |
| 12 Apr 20261:12 pm |
koda-core |
2.39.2 |
Capture Signal send timestamps for outbound message_id; enables quote lookup of Koda replies via exact message_id match instead of content fallback. |
| 12 Apr 202610:24 am |
koda-core |
2.39.1 |
Signal quote context: look up full original message from DB instead of truncated 500-char signal-cli text; add quoted_message_id column to messages table; 4000-char fallback limit. |
| 11 Apr 20269:50 am |
koda-core |
2.39.0 |
RBAC v2: four-role hierarchy (owner/admin/operator/viewer), per-workspace permission resolution, binary gate, cascading role management, workspace user API endpoints, Signal workspace role commands. |
| 11 Apr 20268:19 am |
dashboard-generator |
1.7.2 |
Add v2.0 and v3.0 spec document links to build page Software card; copy v3.0 spec to site directory. |
| 10 Apr 202610:54 pm |
koda-console |
1.25.2 |
Fix edit modal: prevent horizontal overflow, stack priority/date for mobile, use separate availableGroups for form dropdowns (regression from scope pills change). |
| 10 Apr 202610:40 pm |
koda-console |
1.25.1 |
Label taxonomy: approved label chip grid in add/edit forms (bug, feature, maintenance, docs, core, pwa, infra, next-release), custom label fallback preserved. |
| 10 Apr 20269:56 pm |
koda-console |
1.25.0 |
Task filtering: scope pills built from task data (global + app + group), label multi-select OR-filter with toggleable pills, fix template syntax bug (#193 #194 #195). |
| 10 Apr 20269:47 pm |
koda-core |
2.38.2 |
Fix plan mode names in system prompt: auto→semi, full→auto to match actual /continue command modes. |
| 10 Apr 20265:00 pm |
koda-core |
2.38.1 |
Remove stale console.koda.systems from CORS whitelist and dead-end migration comment. |
| 10 Apr 20265:00 pm |
koda-console |
1.24.2 |
Fix manifest URL from dead console.koda.systems to api.koda.systems/console/. |
| 9 Apr 20268:00 pm |
koda-core |
2.36.0 |
Rewrite generatePDF() to use React-PDF pipeline (Mockup D v7 styled output) with Playwright fallback. Supports both structured DocumentContent objects and legacy markdown strings. Adds parseMarkdownToStructured() and structuredToMarkdown() helpers. |
| 9 Apr 20267:00 pm |
koda-console |
1.24.1 |
Detect Cloudflare Access session expiry: use redirect:'manual' on fetch to catch 302 redirects, show amber 'Session expired — tap to re-authenticate' banner instead of silent failure or misleading 'offline' state. |
| 9 Apr 20266:37 pm |
dashboard-generator |
1.7.1 |
Standardise nav bar across all generated pages: all 6 links (Home, Build, Status, Changelog, Flow, Tasks) with active indicator on each page. |
| 9 Apr 20266:37 pm |
koda-core |
2.38.0 |
Rename /continue modes: auto→semi, full→auto (aligns with /work auto naming). Add semi-auto alias. /work auto now prompts for queue selection when multiple active queues exist. Updated docs: SOP task lifecycle, information-flow (auto-queue diagram), task-lifecycle page. |
| 9 Apr 20265:38 pm |
koda-core |
2.37.1 |
Fix duplicate auto-queue notifications (replace signal-notify.sh with services.reply) and message ordering race condition (claimGroup in processNext before async dispatch, bypass queue gate for _fromQueue messages). |
| 9 Apr 20265:12 pm |
koda-core |
2.37.0 |
React-PDF document generation pipeline: professional PDF output with native gradients, Inter/Source Serif typography, inline SVG diagrams, Koda-branded template system. Replaces Playwright-based PDF generation (kept as fallback). End-to-end validated. |
| 9 Apr 20264:22 pm |
koda-core |
2.35.0 |
Unattended work queue execution: /work auto [queue] runs all eligible tasks back-to-back with progress notifications, prerequisite checking, queue status updates, and /stop integration. Refactored task execution into reusable helpers. |
| 9 Apr 202612:34 pm |
koda-core |
2.34.1 |
Fix CLI hang (10s watchdog after result event), browser leak in document-generator (try/finally), PDF fallback filename, extract Melbourne date helper, use fileToAttachment() in research pipeline, fix isGroup detection, parallel doc generation. |
| 9 Apr 202612:44 am |
koda-console |
1.24.0 |
Completed tasks: load all (no limit), search includes done tasks, load-more button. Faster chat polling (5s) during active plans for prompt system message display (#164, #165). |
| 9 Apr 202612:44 am |
koda-core |
2.34.0 |
Session-aware system notifications: /internal/notify endpoint routes messages to PWA session (DB) or Signal based on context. signal-notify.sh calls Core first with Signal API fallback (#165). |
| 9 Apr 202612:15 am |
koda-core |
2.33.0 |
Hybrid research: parallel sub-agent research with document generation pipeline. Timestamped filenames (research-slug-YYYY-MM-DD-HHmm.ext), URLs via file-share system, new research-publish.js script. |
| 8 Apr 20268:31 pm |
koda-console |
1.23.1 |
PWA UX fixes: bigger/brighter refresh icon in header (#161), dropdown arrow no longer clipped off-page (#162), tighter task list spacing (#163). |
| 8 Apr 20267:20 pm |
koda-console |
1.23.0 |
Fix session-bleed bug: back button now clears all session state, polling/streaming no longer fall back to stale session IDs, sessionStorage restore includes viewing session context. |
| 8 Apr 20267:06 pm |
koda-console |
1.22.1 |
Fix SW unregistered after force reload: log registration errors instead of silently swallowing, clean up ?cb= cache-buster query param from URL. |
| 8 Apr 20265:57 pm |
koda-core |
2.32.0 |
Suppress redundant startup/shutdown DMs during plan-driven restarts; improve self-modify restart messages with timing estimates, auto-mode differentiation, and cleaner CTAs (#125). |
| 8 Apr 20265:57 pm |
koda-console |
1.22.0 |
Enter to send on desktop chat (#158), remove unnecessary New Chat confirmation dialog (#159). |
| 8 Apr 20264:20 pm |
koda-console |
1.21.1 |
Fix chat rename for new sessions: populate chatViewingSession on session ID arrival, add pencil icon to header for rename discoverability. |
| 8 Apr 20264:08 pm |
koda-console |
1.21.0 |
Fix chat message display: slash commands show as command text (not expanded prompts), system messages persisted and styled distinctly, command messages get outlined bubble style. |
| 8 Apr 20264:08 pm |
koda-core |
2.31.0 |
Add msg_type column to messages table, accept displayAs param in chat send endpoint, persist slash command text and system messages to DB, return msgType in history API. |
| 8 Apr 20263:32 pm |
koda-console |
1.20.0 |
Fix PWA chat session isolation — pass explicit groupId in viewSession (#120). Add 'Work on this' button to instruction detail view (#130). |
| 8 Apr 20263:32 pm |
koda-core |
2.30.0 |
Fix chat rename bug: remove unsafe cross-session fallback in autoNameLatestSession, add overlap guard to history timestamp fallback, delay auto-naming for backfill (#120). Enhance auto-naming with Claude-generated titles for 10+ message sessions (#157). |
| 8 Apr 20261:41 pm |
koda-console |
1.19.1 |
Fix 3 PWA bugs: task list horizontal overflow on mobile, completed tasks not tappable, chat rename keyboard not appearing on mobile (user-gesture focus fix). |
| 8 Apr 20261:15 pm |
koda-core |
2.29.1 |
Branded 404 page for expired/invalid share links (CF Pages + Core route). Directory listing guard on site/shared/. |
| 8 Apr 202612:02 pm |
koda-console |
1.19.0 |
Add Shared Files section to PWA hamburger menu with list, copy link, open, and delete actions. Add sharedFiles API client. |
| 8 Apr 202612:02 pm |
koda-core |
2.29.0 |
File sharing via web links (Task #151). Hybrid architecture: auto-share generated docs to CF Pages CDN, manual /share command for core-hosted files. New shared_files DB table, /api/shared-files REST API, /share/:token public download route, /share Signal command, auto-share in document-generator. |
| 8 Apr 202610:35 am |
koda-core |
2.28.1 |
Archive completed/stopped/cancelled plans to logs/plans/ instead of deleting. PWA poll auto-archives completed plans so banner clears. self-modify.sh archives after last step. /stop and /plan cancel also archive. |
| 8 Apr 20269:45 am |
koda-console |
1.18.0 |
PWA slash commands (/continue, /plan, /stop etc), plan progress banner with live polling, scroll-to-top floating button. Slash commands route through new POST /command endpoint. |
| 8 Apr 20269:45 am |
koda-core |
2.28.0 |
Add POST /api/chat/command and GET /api/chat/plan endpoints for PWA slash commands and plan status. Replace auto-continue ACK with informative plan-aware message. Registry.handle() accepts services override. |
| 8 Apr 20269:00 am |
koda-core |
2.27.6 |
Reduce AI response latency: remove 1.5s batch delay (BATCH_DELAY_MS=0), reuse earlySessionId to eliminate duplicate DB call, parallelise all pre-Claude lookups (instructions, memories, permissions, group type), add 60s in-memory cache for instructions and 30s cache for memories. Also fixed crash-loop from duplicate isOwner declaration. |
| 8 Apr 20267:48 am |
koda-console |
1.17.2 |
Fix cross-chat streaming bleed: scope streaming display (thinking/response/typing bubbles) to the originating session so navigating to another session no longer shows the wrong stream. |
| 7 Apr 202610:30 pm |
koda-core |
2.27.2 |
Queue ack quoting refined: only quote original message when actually queued (not on initial ack). Pre/post change notification rule added to CLAUDE.md. |
| 7 Apr 202610:15 pm |
koda-console |
1.17.1 |
Quick-win PWA batch: rename Running→Running Jobs, tick/cross icons on chat rename, scroll-to-bottom floating button in chat, tap chat tab again to return to session list. |
| 7 Apr 202610:00 pm |
koda-console |
1.17.0 |
Fix PWA chat state loss on iOS background/navigation: graceful stream error recovery preserves partial responses, visibilitychange handler re-fetches history on resume, sessionStorage buffering survives page kills. |
| 7 Apr 202610:00 pm |
koda-core |
2.27.5 |
Chat API emits sessionId as early SSE event before streaming, enabling client recovery when streams break mid-response. |
| 7 Apr 20269:41 pm |
koda-console |
1.16.2 |
Sort session list by last message activity instead of archive/creation time. Simplified time-ago display to use lastActivity field. |
| 7 Apr 20269:41 pm |
koda-core |
2.27.4 |
Session history API now returns lastActivity field (latest message timestamp per session) for accurate sort ordering. |
| 7 Apr 20269:35 pm |
koda-core |
2.27.3 |
Move quote from queue ack to dequeued processing ack — quote now appears when the queued message is actually picked up, not when it's queued. |
| 7 Apr 20269:07 pm |
koda-core |
2.27.1 |
Queue ack quotes the original message via Signal reply, so the user can see what was picked up when processing catches up. |
| 7 Apr 20269:02 pm |
koda-console |
1.16.1 |
Sort PWA chat session list by most recently replied (endedAt desc, with startedAt fallback for current sessions). |
| 7 Apr 20268:45 pm |
koda-console |
1.16.0 |
PWA app badge API (unread count on home screen icon), chatTransport routing fix (PWA→Signal leak), New Chat button moved to session list only, unread count pills in session list, markRead watermark wiring on view/tab-switch/poll. |
| 7 Apr 20268:45 pm |
koda-core |
2.27.0 |
Group-scoped plan files (/tmp/koda-plan-{groupId}.json) to prevent cross-session clobbering. Session reads table + mark-read endpoint + unread count computation in session history API. Scheduler prompt command type. plan-continue endpoint routes PWA vs Signal correctly. |
| 7 Apr 20261:50 pm |
koda-core |
2.26.1 |
Progressive flush hardening: drop dead subtraction logic, serialize concurrent drains via in-flight gate (prevents Signal message reordering), set progressClosed flag in finally to ignore late events from in-flight claude.js callbacks, broaden low-value regex to keep markdown headings and bullets, make verbose-mode debounce respect PROGRESS_FLUSH_DEBOUNCE_MS as a ceiling. |
| 7 Apr 20261:30 pm |
koda-core |
2.26.0 |
Progressive Signal flushes: narration text now reaches Signal at tool-use boundaries with debounced coalescing (PROGRESS_FLUSH_DEBOUNCE_MS, default 1500ms) instead of arriving as one wall after the run finishes. Low-value short segments are filtered. PROGRESS_VERBOSITY env (quiet|normal|verbose) controls behavior. Final completion summary still sent, with already-streamed prose subtracted to avoid duplication. |
| 7 Apr 202612:30 am |
koda-console |
1.15.0 |
Scheduled Tasks UI: create, edit, delete cron-based tasks with preset schedules, enable/disable toggle, manual Run Now, run history with output/error display, and human-readable cron descriptions. Instructions Management UI: list, view/edit, create (with task/queue templates), archive (two-tap), and queue status dropdown management. |
| 7 Apr 202612:30 am |
koda-core |
2.25.0 |
Scheduled task engine: node-cron scheduler with DB-backed tasks, slash command and shell script execution, run history logging, Signal notifications on failure, and full REST API. Instructions API: file CRUD, queue table parser, and status management endpoints. |
| 6 Apr 202611:30 pm |
koda-core |
2.24.0 |
Signal reply/quote support: extract quoted message context from inbound replies (prepended to Claude prompt), add outbound quote plumbing to signalSend/signalSendGroup for future reply-to capability. |
| 6 Apr 20268:35 pm |
koda-core |
2.23.2 |
Fix phantom message bug: Signal WebSocket double-delivers messages with different envelope shapes (sourceNumber vs sourceUuid). Multi-key dedup now registers all sender identifier variants so duplicates are caught regardless of format. |
| 6 Apr 20265:35 pm |
koda-core |
2.23.1 |
Fix session ID cross-contamination: tag inbound messages with sessionId at write time instead of greedy post-hoc backfill that stole messages from other sessions. |
| 6 Apr 20263:45 pm |
koda-core |
2.23.0 |
Rename koda_bridge → koda_core across code, DB role, JWT env var (BRIDGE_JWT → CORE_JWT), postgrest.conf, schema.sql, and docs. Live DB migrated via ALTER ROLE rename. |
| 3 Apr 20261:00 pm |
dashboard-generator |
1.7.0 |
Cross-site design overhaul: WCAG AA contrast fix (--text-dim 4.5:1+), 44px touch targets, focus-visible outlines, tap feedback, bumped font sizes, flow page mobile rewrite (version navigator, card-based history), landing page aria labels, changelog expandable rows with date/time stacking. |
| 3 Apr 202612:00 pm |
koda-core |
2.22.1 |
Fix message queue race condition: move processNext() call to server.js finally block after releaseGroup() to prevent re-queued messages seeing group as still busy. |
| 1 Apr 20269:54 pm |
koda-console |
1.14.0 |
Credentials section in More tab: list stored credentials (masked), add new, delete. No reveal in PWA — use /cred reveal via Signal. |
| 1 Apr 20269:54 pm |
koda-core |
2.22.0 |
/cred list command + /api/credentials endpoints (GET list, POST create, DELETE remove). Owner-only, audit-logged, passwords always masked in API responses. |
| 1 Apr 20269:28 pm |
koda-core |
2.21.0 |
/stop now sends SIGINT to running Claude sessions (Ctrl+C equivalent). Also fixes auto-continue self-messaging bug (#59), auto-archives completed work queues, and updates ack message wording. |
| 1 Apr 20263:55 pm |
koda-console |
1.13.0 |
Task edit converted to slide-up modal with full-size fields, labels editor, split save/cancel buttons (green Save), and safe-area-aware bottom padding. |
| 1 Apr 20263:05 pm |
koda-console |
1.12.0 |
Task UI polish: click-to-edit, task IDs shown, star priority pickers, auto-grow textareas, carry title to detailed form, styled Add button, swapped save/cancel. Chat: Signal banner dismiss-only. |
| 1 Apr 20261:45 pm |
koda-console |
1.11.0 |
Chat UX overhaul: session list as chat home with back arrow nav, click-to-view with lazy resume, pencil rename, active session highlight, Signal banner fixes with notification caveat. |
| 1 Apr 20261:45 pm |
koda-core |
2.20.0 |
Session history API returns live message counts and merges current active sessions with current flag. PWA and Signal sessions unified in one list. |
| 1 Apr 202612:00 pm |
koda-console |
1.9.0 |
Add live elapsed timers to dashboard widgets: 'Updated X ago' on Claude Usage, running duration on active jobs (dashboard + More tab). |
| 1 Apr 202611:30 am |
koda-console |
1.10.0 |
Drop into Signal DM from PWA: session history shows Signal/PWA badges, resume Signal sessions, transport-aware chat with Signal echo. |
| 1 Apr 202611:30 am |
koda-core |
2.19.0 |
Signal styled text mode on all responses, session history serves both Signal and PWA sessions, chat API transport param echoes responses to Signal DM. |
| 1 Apr 202610:00 am |
koda-core |
2.18.0 |
Fix session history: backfill claude_session_id on PWA chat messages, deduplicate archiveSession, stop resetting sessions on shutdown, clean up stale history entries. |
| 31 Mar 20268:58 pm |
deploy-watch |
1.1.0 |
Fetch Cloudflare API token from credential store via get-cred.js instead of inline .env decrypt. Retry loop for PostgREST availability at boot. |
| 31 Mar 20268:57 pm |
koda-core |
2.17.0 |
Fetch runtime secrets (Cloudflare API token, CF Access service token) from encrypted credential store at startup instead of .env. New get-cred.js utility for shell scripts. |
| 31 Mar 20266:00 pm |
koda-core |
2.16.0 |
Per-group message queuing with cancel support, message dedup at ingestion (60s TTL), 10min queue expiry, auto-process next queued message after session ends. Removed dead /webhook route. |
| 31 Mar 20269:02 am |
koda-console |
1.8.0 |
PWA chat batch: markdown rendering (marked.js + DOMPurify), streaming thinking blocks with collapsible UI, unread message badge on Chat tab, Enter for newline / Cmd+Enter to send, tighter bubble spacing, cross-device polling (30s). |
| 31 Mar 20269:02 am |
koda-core |
2.15.1 |
Add onThinking callback to Claude invocation for streaming thinking blocks via SSE to PWA chat. |
| 31 Mar 20267:55 am |
koda-console |
1.7.0 |
PWA nav batch: swap Chat/Tasks tabs, always-visible active jobs, task sorting, delete archived sessions, pull-to-refresh, hamburger popup menu, iOS zoom fix. |
| 31 Mar 20261:45 am |
koda-core |
2.15.0 |
Hot-reload infrastructure: lib modules can now be reloaded without restarting Koda Core. self-modify.sh tries /internal/hot-reload before falling back to restart. Includes auto-continue, /stop kill switch, and plan-aware notifications from autonomy-01. |
| 31 Mar 202612:15 am |
koda-core |
2.14.1 |
Fix usage scrape failing after restart by clearing stale Chromium SingletonLock before launching browser. |
| 30 Mar 20266:55 pm |
koda-console |
1.6.2 |
Chat UX: anchor input bar above tab bar, round send button with grey-when-empty to indigo-when-ready state change, filled send icon. |
| 30 Mar 20266:50 pm |
koda-console |
1.6.1 |
Fix iOS PWA: move safe-area padding to app shell so update/offline banners clear the notch. Prevent auto-zoom on input focus with maximum-scale=1. |
| 30 Mar 20266:45 pm |
koda-console |
1.6.0 |
Session history browser: slide-up drawer in Chat tab to view, rename, and resume past sessions. Active session tracking for context continuity across resume. |
| 30 Mar 20266:30 pm |
koda-core |
2.14.0 |
Session history & resume: archive sessions on reset, list/rename/resume API, auto-naming from first message, session_id filtering on chat history, claude_session_id tracking on messages. |
| 30 Mar 20266:15 pm |
koda-console |
1.5.1 |
Fix PWA update banner: remove skipWaiting() from SW install so new versions enter waiting state and trigger the update banner. Remove client.navigate() from activate — reload handled by controllerchange listener. |
| 30 Mar 20266:00 pm |
koda-console |
1.5.0 |
Console UX overhaul: 4-tab nav (Sessions+Settings moved into More), running jobs on dashboard, sticky Select button, scope pills hidden when only All/Global, cleaned up test data from groups and memories. |
| 30 Mar 202610:35 am |
koda-console |
1.4.1 |
Transitional SW bootstrap: restore skipWaiting() so existing mobile clients (without update banner) can receive the new SW with network-first HTML and update banner code. |
| 30 Mar 202610:26 am |
koda-console |
1.4.0 |
PWA update banner: 'New version available — tap to update' shown when a new service worker is waiting. Network-first strategy for HTML ensures fresh content. SW template read per-request (no restart needed for SW changes). |
| 30 Mar 202610:26 am |
koda-core |
2.13.2 |
Read sw.js template per-request instead of caching at startup — SW file changes no longer require Koda Core restart. |
| 30 Mar 20269:46 am |
koda-console |
1.3.0 |
Console UX improvements: pill-styled Select button, friendly session names (not UUIDs), consolidated single task list (removed 248-line duplicate), auto-versioned SW cache from versions.json. |
| 30 Mar 20269:46 am |
koda-core |
2.13.1 |
Sessions API returns displayName (group name or user DM label), /status sessions uses user lookup for DM labels, /console/sw.js route injects version-based cache name. |
| 29 Mar 202611:00 pm |
koda-core |
2.12.1 |
Fix duplicate Signal notifications during multi-step plans: suppress redundant self-modify.sh, startup/shutdown, and progress notifications. |
| 29 Mar 202610:30 pm |
koda-console |
1.2.0 |
Add Chat tab to Console PWA — real-time streaming conversation with Claude via SSE, message history, typing indicators, new chat/reset, 6-column tab bar. |
| 29 Mar 202610:30 pm |
koda-core |
2.13.0 |
Add /api/chat endpoints — SSE streaming chat (send/history/reset) with correct system prompt building, session continuity, and message persistence. |
| 29 Mar 20268:30 pm |
koda-console |
1.1.0 |
Add dedicated Tasks tab to Console PWA — scope filter pills, quick/full add forms, task cards with priority/labels/due dates, inline edit, multi-select with bulk done/delete, completed section. |
| 29 Mar 20268:30 pm |
koda-core |
2.12.0 |
Scoped task management: upgraded tasks DB schema (scope, priority, labels, due_date), /api/tasks REST endpoints (CRUD + bulk + work-on-it), enhanced /task command (scope-aware, flags, multi-ID done, edit, delete, instruction file generation via /task work). |
| 29 Mar 20266:25 pm |
koda-core |
2.11.0 |
Mount Koda Console PWA on Koda Core at /console/ (Tier 1 same-origin serving). Eliminates cross-origin CF Access cookie issues. |
| 29 Mar 20265:30 pm |
koda-core |
2.10.1 |
Fix CF_TEAM_DOMAIN fallback default from 'koda' to 'koda-systems' in middleware.js. Fix race condition in self-modify.sh where intentional-restart sentinel was created too late, causing 'interrupted' message on planned restarts. |
| 29 Mar 20262:30 pm |
koda-core |
2.10.0 |
NL intent pipeline: lib/intents.js parses koda-intent blocks from Claude responses, validates against app manifests, sends confirmation prompts via Signal, executes against app Worker APIs, audit logs to intent_log table. Yes/no intercept in message handler. |
| 29 Mar 20262:20 pm |
koda-core |
2.9.0 |
App registration + intent context: lib/apps.js registry module with manifest validation and in-memory cache, intent context injection into Claude system prompts, /api/apps/intents and /api/intents endpoints, intent_log DB table, DB conventions documentation, scaffold-app.sh updated with standard D1 tables and helpers. |
| 29 Mar 202612:15 pm |
koda-core |
2.8.0 |
Standalone app framework (Tier 2): /api/apps CRUD endpoints, koda_apps DB table, scaffold-app.sh and deploy-app.sh scripts for Cloudflare Workers + D1 apps. |
| 29 Mar 202611:30 am |
koda-console |
1.0.0 |
Initial release: Koda Console PWA deployed to console.koda.systems. Alpine.js + HTMX + Tailwind (no build step). 4-tab layout: Dashboard, Sessions, Settings, More. PWA installable on iPhone. Service worker shell caching. |
| 29 Mar 202611:00 am |
koda-core |
2.7.0 |
REST API for Console PWA: /api/* endpoints (auth, status, usage, sessions, model, route, memories, users, messages, groups, jobs, config). JWT auth via Cloudflare Access. DB migration: email column on users. |
| 29 Mar 202610:30 am |
koda-core |
2.6.0 |
Cloudflare Tunnel (api.koda.systems) + Access auth + CORS middleware for /api routes |
| 26 Mar 202610:45 am |
koda-core |
2.5.0 |
Fix attachment saving: fetch data from signal-cli REST API by attachment ID instead of expecting inline base64. Add long message support: detect text/x-signal-plain attachments and replace truncated message body with full text. |
| 26 Mar 20268:55 am |
koda-core |
2.4.0 |
Group messaging fixes: Signal group send format (group.base64), mention detection (U+FFFC + mentions array), group name sync on startup, friendly names in /status sessions, signal-notify.sh group format detection, /plan and /continue commands for multi-step plan continuation across restarts |
| 20 Mar 20267:00 pm |
koda-core |
2.3.0 |
Restart notifications with reason context (pre-restart, shutdown, startup messages include modified file), signal-notify.sh reusable wrapper, mandatory progress reporting instructions for multi-step plans via Signal |
| 20 Mar 20265:45 pm |
koda-core |
2.2.0 |
Group web publishing and research assistant features: /activate with group type registry, /pages, /research, /jobs commands, background task runner, multi-format document generation (PDF/PPTX/DOCX/MD), Signal file delivery, RBAC capabilities (web_publish/web_edit/web_delete/research), group-type-specific skill auto-loading, page-design-guide MCP integration |
| 20 Mar 202612:30 pm |
dashboard-generator |
1.6.0 |
Glassmorphism redesign for status page: glass cards with backdrop-filter, ambient background orbs, service pills, gradient hardware metrics, reduced-motion support |
| 20 Mar 202612:00 pm |
koda-core |
2.1.0 |
Rename service com.koda.claude-bridge → com.koda.core, exec fix for PID alignment, log files bridge-* → core-* |
| 19 Mar 202610:00 pm |
koda-core |
2.0.0 |
Modular decomposition: server.js split into 16 lib modules + 18 hot-swappable command handlers with fs.watch auto-reload |
| 19 Mar 20262:00 pm |
vscode-bridge |
0.2.4 |
Replace exec with execFile in SearchFilesTool, add command logging (audit-011) |
| 19 Mar 20261:30 pm |
deploy-watch |
1.0.1 |
Shell hardening and config cleanup (audit-010) |
| 19 Mar 20261:00 pm |
koda-core |
1.9.3 |
Remove unused dependencies crypto, body-parser, nodemailer (audit-009) |
| 19 Mar 202612:30 pm |
koda-core |
1.9.2 |
Replace fragile 15s wait with selector-based wait in usage scraper (audit-008) |
| 19 Mar 202612:00 pm |
koda-core |
1.9.1 |
Add input validation for slash command arguments (audit-007) |
| 19 Mar 202611:30 am |
koda-core |
1.9.0 |
Server resilience improvements — file permission health check, startup validation (audit-006) |
| 19 Mar 202610:30 am |
koda-core |
1.8.10 |
Mask credentials in /cred get, add /cred reveal command (audit-003) |
| 19 Mar 202610:00 am |
koda-core |
1.8.9 |
Require Authorization header on /db proxy endpoint (audit-002) |
| 19 Mar 20269:30 am |
dashboard-generator |
1.5.0 |
Upgrade auth from SHA-256 to PBKDF2, move hash to env var (audit-004) |
| 19 Mar 20265:00 am |
koda-core |
1.8.8 |
Version key rename signal-bridge → koda-core in versions.json |
| 19 Mar 20264:00 am |
koda-core |
1.8.7 |
Show route and model in 'working on that' acknowledgement message |
| 19 Mar 20263:00 am |
koda-core |
1.8.4 |
Enhance /reset reply with route, model, and usage info; AI-authored deploy confirmations |
| 19 Mar 20262:00 am |
koda-core |
1.8.3 |
Fix /model clear route label, enhance /model clear to show route and model info |
| 19 Mar 20261:30 am |
koda-core |
1.8.1 |
Fix version reading from koda-core key instead of signal-bridge (was undefined) |
| 19 Mar 20261:00 am |
koda-core |
1.8.0 |
Implement /route and /model dual-command system for Claude CLI and VS Code routing |
| 19 Mar 202612:00 am |
dashboard-generator |
1.4.0 |
Add Information Flow page (/flow) — SVG flowchart of Signal→Koda→Claude message path |
| 18 Mar 20269:00 pm |
koda-core |
1.7.5 |
Sentinel file approach: intentional restarts send 'restarting' instead of 'interrupted' |
| 18 Mar 20268:00 pm |
dashboard-generator |
1.3.1 |
Rename 'Bridge' → 'Koda Core' in service names, page title, and subtitle |
| 18 Mar 20267:00 pm |
koda-core |
1.7.4 |
Fix stripMarkdown mangling underscore-delimited identifiers (e.g. ENV_VAR_NAMES) |
| 18 Mar 20266:30 pm |
koda-core |
1.7.3 |
/status: show timeout value and elapsed time per active session; add deployment notification script |
| 18 Mar 20266:00 pm |
koda-core |
1.7.2 |
/status: remove auto-approve/approvals, unify service labels, show model and usage for active route |
| 18 Mar 20265:45 pm |
koda-core |
1.7.1 |
Deduplicate shutdown notifications when owner has an active session |
| 18 Mar 20265:30 pm |
dashboard-generator |
1.3.0 |
Add changelog page, version timestamps on status page, 60s auto-refresh via launchd cron |
| 18 Mar 20265:30 pm |
koda-core |
1.7.0 |
15-minute default timeout, suppress duplicate errors on shutdown, fix owner error notification to use UUID |
| 18 Mar 20266:15 am |
koda-core |
1.6.1 |
Use OWNER_UUID directly for all owner notifications instead of phone lookup |
| 18 Mar 20266:10 am |
koda-core |
1.6.0 |
Notify active sessions on bridge shutdown; track active sessions by group |
| 18 Mar 20265:30 am |
koda-core |
1.5.2 |
Friendlier error messages for Claude exit code 143/137 (SIGTERM/SIGKILL) |
| 18 Mar 20265:25 am |
koda-core |
1.5.1 |
Show bridge version in /status command output |
| 18 Mar 20265:20 am |
koda-core |
1.5.0 |
Auto-approve default on, /model display rework, two-phase /modify flow |
| 17 Mar 202610:00 pm |
vscode-bridge |
0.2.3 |
Fix filesystem write failures using direct local tool invocation |
| 17 Mar 20268:00 pm |
vscode-bridge |
0.2.2 |
Model deduplication in VS Code bridge |
| 17 Mar 20266:00 pm |
dashboard-generator |
1.2.0 |
Three-page dashboard (landing, build, status); mobile-first CSS; clickable sections |
| 17 Mar 20264:00 pm |
koda-core |
1.4.2 |
Graceful shutdown cleanup to prevent process hangs |
| 16 Mar 202612:00 pm |
deploy-watch |
1.0.0 |
Initial release — fswatch + Cloudflare Pages deploy pipeline |