Compare commits

..

5 Commits

10 changed files with 1389 additions and 44 deletions

View File

@@ -1,10 +1,10 @@
{
"last_dolt_commit": "toe8dna9743n523thnefrp1ddnu8blag",
"last_dolt_commit": "6bdv5ku8o7amuah7crbk8vnf4tmai8m6",
"last_event_id": 0,
"timestamp": "2026-03-02T03:34:56.2779025Z",
"timestamp": "2026-03-02T03:52:13.6279948Z",
"counts": {
"issues": 10,
"events": 22,
"events": 29,
"comments": 0,
"dependencies": 14,
"labels": 0,

View File

@@ -20,3 +20,10 @@
{"actor":"Ed_","comment":null,"created_at":"2026-03-01T22:31:21Z","event_type":"closed","id":20,"issue_id":"rook-43b","new_value":"{\"status\":\"closed\"}","old_value":"{\"id\":\"rook-43b\",\"title\":\"File capability: read/write/edit with backup\",\"description\":\"File operations with backup-before-edit. Allowlist for working dirs. No destructive deletes without confirmation. Type-hinted API.\",\"status\":\"in_progress\",\"priority\":2,\"issue_type\":\"task\",\"assignee\":\"Ed_\",\"owner\":\"edwardgz@gmail.com\",\"created_at\":\"2026-03-02T02:14:31Z\",\"created_by\":\"Ed_\",\"updated_at\":\"2026-03-02T03:28:35Z\"}"}
{"actor":"Ed_","comment":null,"created_at":"2026-03-01T22:31:25Z","event_type":"claimed","id":21,"issue_id":"rook-wka","new_value":"{\"assignee\":\"Ed_\",\"status\":\"in_progress\"}","old_value":"{\"id\":\"rook-wka\",\"title\":\"Git capability: git ops with policy allowlist\",\"description\":\"Git operations (status, diff, add, commit, log). Allowlist gates on push/reset/force ops. Confirmation required for destructive git commands.\",\"status\":\"open\",\"priority\":2,\"issue_type\":\"task\",\"owner\":\"edwardgz@gmail.com\",\"created_at\":\"2026-03-02T02:14:32Z\",\"created_by\":\"Ed_\",\"updated_at\":\"2026-03-02T02:14:32Z\"}"}
{"actor":"Ed_","comment":null,"created_at":"2026-03-01T22:34:56Z","event_type":"closed","id":22,"issue_id":"rook-wka","new_value":"{\"status\":\"closed\"}","old_value":"{\"id\":\"rook-wka\",\"title\":\"Git capability: git ops with policy allowlist\",\"description\":\"Git operations (status, diff, add, commit, log). Allowlist gates on push/reset/force ops. Confirmation required for destructive git commands.\",\"status\":\"in_progress\",\"priority\":2,\"issue_type\":\"task\",\"assignee\":\"Ed_\",\"owner\":\"edwardgz@gmail.com\",\"created_at\":\"2026-03-02T02:14:32Z\",\"created_by\":\"Ed_\",\"updated_at\":\"2026-03-02T03:31:26Z\"}"}
{"actor":"Ed_","comment":null,"created_at":"2026-03-01T22:36:47Z","event_type":"updated","id":23,"issue_id":"rook-8o3","new_value":"{\"description\":\"Discord bot daemon thread. Poll for messages and audio attachments. Pass audio through STT. Route transcribed text to agent queue via asyncio.run_coroutine_threadsafe(). Use discord.py (pip: discord.py). Bot token from DISCORD_BOT_TOKEN env var. Listen in a designated channel.\",\"title\":\"Discord integration: polling, audio STT input\"}","old_value":"{\"id\":\"rook-8o3\",\"title\":\"Telegram integration: polling, audio STT input\",\"description\":\"Telegram bot daemon thread. Poll for messages and audio notes. Pass audio through STT. Route transcribed text to agent queue via asyncio.run_coroutine_threadsafe().\",\"status\":\"open\",\"priority\":2,\"issue_type\":\"task\",\"owner\":\"edwardgz@gmail.com\",\"created_at\":\"2026-03-02T02:14:30Z\",\"created_by\":\"Ed_\",\"updated_at\":\"2026-03-02T02:14:30Z\"}"}
{"actor":"Ed_","comment":null,"created_at":"2026-03-01T22:37:49Z","event_type":"claimed","id":24,"issue_id":"rook-11u","new_value":"{\"assignee\":\"Ed_\",\"status\":\"in_progress\"}","old_value":"{\"id\":\"rook-11u\",\"title\":\"CoSy integration: subprocess pipe to CoSy.bat/reva.exe\",\"description\":\"Launch CoSy as persistent subprocess via Popen. stdin/stdout pipe. Send Forth-APL expressions, read results. Backup before redefining words. Only load from personal vocab dir.\",\"status\":\"open\",\"priority\":2,\"issue_type\":\"task\",\"owner\":\"edwardgz@gmail.com\",\"created_at\":\"2026-03-02T02:14:32Z\",\"created_by\":\"Ed_\",\"updated_at\":\"2026-03-02T02:14:32Z\"}"}
{"actor":"Ed_","comment":null,"created_at":"2026-03-01T22:39:54Z","event_type":"closed","id":25,"issue_id":"rook-11u","new_value":"{\"status\":\"closed\"}","old_value":"{\"id\":\"rook-11u\",\"title\":\"CoSy integration: subprocess pipe to CoSy.bat/reva.exe\",\"description\":\"Launch CoSy as persistent subprocess via Popen. stdin/stdout pipe. Send Forth-APL expressions, read results. Backup before redefining words. Only load from personal vocab dir.\",\"status\":\"in_progress\",\"priority\":2,\"issue_type\":\"task\",\"assignee\":\"Ed_\",\"owner\":\"edwardgz@gmail.com\",\"created_at\":\"2026-03-02T02:14:32Z\",\"created_by\":\"Ed_\",\"updated_at\":\"2026-03-02T03:37:50Z\"}"}
{"actor":"Ed_","comment":null,"created_at":"2026-03-01T22:45:48Z","event_type":"updated","id":26,"issue_id":"rook-iwn","new_value":"{\"description\":\"GUI using imgui-bundle (NOT dearpygui — multi-viewport issues). Dockable panels: agent transcript, TTS status, CoSy output. Dark theme. Runs on main thread; asyncio on background daemon thread. Use imgui_bundle.imgui and imgui_bundle.hello_imgui. No dearpygui anywhere.\",\"title\":\"imgui-bundle GUI: dockable panels for agent transcript and status\"}","old_value":"{\"id\":\"rook-iwn\",\"title\":\"Dear PyGui GUI: ModernCoSy dockable panels\",\"description\":\"Dear PyGui dark-theme GUI. Dockable panels for agent transcript, TTS status, CoSy output. Runs on main thread; asyncio on background daemon thread.\",\"status\":\"open\",\"priority\":2,\"issue_type\":\"task\",\"owner\":\"edwardgz@gmail.com\",\"created_at\":\"2026-03-02T02:14:32Z\",\"created_by\":\"Ed_\",\"updated_at\":\"2026-03-02T02:14:32Z\"}"}
{"actor":"Ed_","comment":null,"created_at":"2026-03-01T22:45:48Z","event_type":"updated","id":27,"issue_id":"rook-8o3","new_value":"{\"description\":\"Discord bot daemon thread. Poll for messages and audio attachments. Pass audio through STT. STT backend pluggable via STT_BACKEND env var: 'whisper' uses openai.Audio.transcribe (OPENAI_API_KEY), 'google' uses google.cloud.speech (GOOGLE_STT_KEY). Route transcribed text to agent queue via asyncio.run_coroutine_threadsafe(). Use discord.py. Bot token from DISCORD_BOT_TOKEN env var. Listen in a designated channel.\"}","old_value":"{\"id\":\"rook-8o3\",\"title\":\"Discord integration: polling, audio STT input\",\"description\":\"Discord bot daemon thread. Poll for messages and audio attachments. Pass audio through STT. Route transcribed text to agent queue via asyncio.run_coroutine_threadsafe(). Use discord.py (pip: discord.py). Bot token from DISCORD_BOT_TOKEN env var. Listen in a designated channel.\",\"status\":\"open\",\"priority\":2,\"issue_type\":\"task\",\"owner\":\"edwardgz@gmail.com\",\"created_at\":\"2026-03-02T02:14:30Z\",\"created_by\":\"Ed_\",\"updated_at\":\"2026-03-02T03:36:47Z\"}"}
{"actor":"Ed_","comment":null,"created_at":"2026-03-01T22:48:40Z","event_type":"claimed","id":28,"issue_id":"rook-hi1","new_value":"{\"assignee\":\"Ed_\",\"status\":\"in_progress\"}","old_value":"{\"id\":\"rook-hi1\",\"title\":\"TTS output: ElevenLabs Rook voice + Google TTS fallback\",\"description\":\"Speak agent responses. ElevenLabs TTS with Rook voice ID (scratchy/military). Google TTS fallback. Async playback so it doesn't block the agent loop.\",\"status\":\"open\",\"priority\":2,\"issue_type\":\"task\",\"owner\":\"edwardgz@gmail.com\",\"created_at\":\"2026-03-02T02:14:30Z\",\"created_by\":\"Ed_\",\"updated_at\":\"2026-03-02T02:14:30Z\"}"}
{"actor":"Ed_","comment":null,"created_at":"2026-03-01T22:52:13Z","event_type":"closed","id":29,"issue_id":"rook-hi1","new_value":"{\"status\":\"closed\"}","old_value":"{\"id\":\"rook-hi1\",\"title\":\"TTS output: ElevenLabs Rook voice + Google TTS fallback\",\"description\":\"Speak agent responses. ElevenLabs TTS with Rook voice ID (scratchy/military). Google TTS fallback. Async playback so it doesn't block the agent loop.\",\"status\":\"in_progress\",\"priority\":2,\"issue_type\":\"task\",\"assignee\":\"Ed_\",\"owner\":\"edwardgz@gmail.com\",\"created_at\":\"2026-03-02T02:14:30Z\",\"created_by\":\"Ed_\",\"updated_at\":\"2026-03-02T03:48:41Z\"}"}

View File

@@ -1,10 +1,10 @@
{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"","closed_at":null,"closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"4e573e258c203f88413b3876a48280d60400783e45ba3b7075009cafc674f565","created_at":"2026-03-02T02:14:32Z","created_by":"Ed_","crystallizes":0,"defer_until":null,"description":"Launch CoSy as persistent subprocess via Popen. stdin/stdout pipe. Send Forth-APL expressions, read results. Backup before redefining words. Only load from personal vocab dir.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"rook-11u","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"edwardgz@gmail.com","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"open","target":"","timeout_ns":0,"title":"CoSy integration: subprocess pipe to CoSy.bat/reva.exe","updated_at":"2026-03-02T02:14:32Z","waiters":"","wisp_type":"","work_type":""}
{"acceptance_criteria":"","actor":"","agent_state":"","assignee":"Ed_","await_id":"","await_type":"","close_reason":"","closed_at":"2026-03-02T03:39:55Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"4e573e258c203f88413b3876a48280d60400783e45ba3b7075009cafc674f565","created_at":"2026-03-02T02:14:32Z","created_by":"Ed_","crystallizes":0,"defer_until":null,"description":"Launch CoSy as persistent subprocess via Popen. stdin/stdout pipe. Send Forth-APL expressions, read results. Backup before redefining words. Only load from personal vocab dir.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"rook-11u","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"edwardgz@gmail.com","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"CoSy integration: subprocess pipe to CoSy.bat/reva.exe","updated_at":"2026-03-02T03:39:55Z","waiters":"","wisp_type":"","work_type":""}
{"acceptance_criteria":"","actor":"","agent_state":"","assignee":"Ed_","await_id":"","await_type":"","close_reason":"","closed_at":"2026-03-02T03:31:21Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"70a42353906165120ed7320f2673e1b136641edcc94bac65c291ceb56329b156","created_at":"2026-03-02T02:14:31Z","created_by":"Ed_","crystallizes":0,"defer_until":null,"description":"File operations with backup-before-edit. Allowlist for working dirs. No destructive deletes without confirmation. Type-hinted API.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"rook-43b","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"edwardgz@gmail.com","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"File capability: read/write/edit with backup","updated_at":"2026-03-02T03:31:21Z","waiters":"","wisp_type":"","work_type":""}
{"acceptance_criteria":"","actor":"","agent_state":"","assignee":"Ed_","await_id":"","await_type":"","close_reason":"","closed_at":"2026-03-02T03:28:26Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"4c5f1fdc2ddb1c860cc8f07897a384cc32f8adbd99dd0914128a747e4b439dc3","created_at":"2026-03-02T02:14:31Z","created_by":"Ed_","crystallizes":0,"defer_until":null,"description":"Run shell commands with policy checks. Allowlist of approved dirs and commands. confirm_spawn() gate. Log all subprocess calls with timestamps.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"rook-6zs","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"edwardgz@gmail.com","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Shell capability: safe subprocess execution","updated_at":"2026-03-02T03:28:26Z","waiters":"","wisp_type":"","work_type":""}
{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"","closed_at":null,"closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"484cebdc8e9e90f14a3dab875ebabff4591a3be96365feaa5a3c565d88de9557","created_at":"2026-03-02T02:14:30Z","created_by":"Ed_","crystallizes":0,"defer_until":null,"description":"Telegram bot daemon thread. Poll for messages and audio notes. Pass audio through STT. Route transcribed text to agent queue via asyncio.run_coroutine_threadsafe().","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"rook-8o3","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"edwardgz@gmail.com","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"open","target":"","timeout_ns":0,"title":"Telegram integration: polling, audio STT input","updated_at":"2026-03-02T02:14:30Z","waiters":"","wisp_type":"","work_type":""}
{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"","closed_at":null,"closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"484cebdc8e9e90f14a3dab875ebabff4591a3be96365feaa5a3c565d88de9557","created_at":"2026-03-02T02:14:30Z","created_by":"Ed_","crystallizes":0,"defer_until":null,"description":"Discord bot daemon thread. Poll for messages and audio attachments. Pass audio through STT. STT backend pluggable via STT_BACKEND env var: 'whisper' uses openai.Audio.transcribe (OPENAI_API_KEY), 'google' uses google.cloud.speech (GOOGLE_STT_KEY). Route transcribed text to agent queue via asyncio.run_coroutine_threadsafe(). Use discord.py. Bot token from DISCORD_BOT_TOKEN env var. Listen in a designated channel.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"rook-8o3","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"edwardgz@gmail.com","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"open","target":"","timeout_ns":0,"title":"Discord integration: polling, audio STT input","updated_at":"2026-03-02T03:45:49Z","waiters":"","wisp_type":"","work_type":""}
{"acceptance_criteria":"","actor":"","agent_state":"","assignee":"Ed_","await_id":"","await_type":"","close_reason":"","closed_at":"2026-03-02T02:38:14Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"281bb6b78c0987562762da2cafe040ff3a9e6c3b00f429a978eaf10810c336cb","created_at":"2026-03-02T02:14:29Z","created_by":"Ed_","crystallizes":0,"defer_until":null,"description":"Initialize Python project. pyproject.toml with deps (anthropic, elevenlabs, python-telegram-bot, dearpygui). Set up src/rook/ package. Configure uv.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"rook-bfj","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"edwardgz@gmail.com","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Project scaffolding: pyproject.toml, src layout, uv setup","updated_at":"2026-03-02T02:38:14Z","waiters":"","wisp_type":"","work_type":""}
{"acceptance_criteria":"","actor":"","agent_state":"","assignee":"Ed_","await_id":"","await_type":"","close_reason":"","closed_at":"2026-03-02T03:18:05Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"0d909735f985b5028b9f49b5430097b81a618db45fb77ae7998ec418cfd31216","created_at":"2026-03-02T02:14:29Z","created_by":"Ed_","crystallizes":0,"defer_until":null,"description":"Core policy module. Allowlists per capability (file, shell, git). confirm_spawn() gate for risky ops. backup_before_edit() helper. Approved working dirs enforcement.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"rook-dn4","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"edwardgz@gmail.com","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Policy engine: allowlists, confirm gates, backup-before-edit","updated_at":"2026-03-02T03:18:05Z","waiters":"","wisp_type":"","work_type":""}
{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"","closed_at":null,"closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"5b9bc779e38b599a57edb6a2374a863a62adc559b106e23e6d266e8b7a7c9079","created_at":"2026-03-02T02:14:30Z","created_by":"Ed_","crystallizes":0,"defer_until":null,"description":"Speak agent responses. ElevenLabs TTS with Rook voice ID (scratchy/military). Google TTS fallback. Async playback so it doesn't block the agent loop.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"rook-hi1","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"edwardgz@gmail.com","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"open","target":"","timeout_ns":0,"title":"TTS output: ElevenLabs Rook voice + Google TTS fallback","updated_at":"2026-03-02T02:14:30Z","waiters":"","wisp_type":"","work_type":""}
{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"","closed_at":null,"closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"20afcd532ae263f8dbd89ebcc93dd71177c9f9f5d185dc716b9319b353fbf6b1","created_at":"2026-03-02T02:14:32Z","created_by":"Ed_","crystallizes":0,"defer_until":null,"description":"Dear PyGui dark-theme GUI. Dockable panels for agent transcript, TTS status, CoSy output. Runs on main thread; asyncio on background daemon thread.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"rook-iwn","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"edwardgz@gmail.com","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"open","target":"","timeout_ns":0,"title":"Dear PyGui GUI: ModernCoSy dockable panels","updated_at":"2026-03-02T02:14:32Z","waiters":"","wisp_type":"","work_type":""}
{"acceptance_criteria":"","actor":"","agent_state":"","assignee":"Ed_","await_id":"","await_type":"","close_reason":"","closed_at":"2026-03-02T03:52:14Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"5b9bc779e38b599a57edb6a2374a863a62adc559b106e23e6d266e8b7a7c9079","created_at":"2026-03-02T02:14:30Z","created_by":"Ed_","crystallizes":0,"defer_until":null,"description":"Speak agent responses. ElevenLabs TTS with Rook voice ID (scratchy/military). Google TTS fallback. Async playback so it doesn't block the agent loop.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"rook-hi1","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"edwardgz@gmail.com","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"TTS output: ElevenLabs Rook voice + Google TTS fallback","updated_at":"2026-03-02T03:52:14Z","waiters":"","wisp_type":"","work_type":""}
{"acceptance_criteria":"","actor":"","agent_state":"","assignee":null,"await_id":"","await_type":"","close_reason":"","closed_at":null,"closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"20afcd532ae263f8dbd89ebcc93dd71177c9f9f5d185dc716b9319b353fbf6b1","created_at":"2026-03-02T02:14:32Z","created_by":"Ed_","crystallizes":0,"defer_until":null,"description":"GUI using imgui-bundle (NOT dearpygui — multi-viewport issues). Dockable panels: agent transcript, TTS status, CoSy output. Dark theme. Runs on main thread; asyncio on background daemon thread. Use imgui_bundle.imgui and imgui_bundle.hello_imgui. No dearpygui anywhere.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"rook-iwn","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"edwardgz@gmail.com","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"open","target":"","timeout_ns":0,"title":"imgui-bundle GUI: dockable panels for agent transcript and status","updated_at":"2026-03-02T03:45:48Z","waiters":"","wisp_type":"","work_type":""}
{"acceptance_criteria":"","actor":"","agent_state":"","assignee":"Ed_","await_id":"","await_type":"","close_reason":"","closed_at":"2026-03-02T03:34:56Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"b1d9f7aa2ce83b5c655f8f56cb29fba8a3e73f09fa1522bd56a1db9cd9206d57","created_at":"2026-03-02T02:14:32Z","created_by":"Ed_","crystallizes":0,"defer_until":null,"description":"Git operations (status, diff, add, commit, log). Allowlist gates on push/reset/force ops. Confirmation required for destructive git commands.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"rook-wka","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"edwardgz@gmail.com","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Git capability: git ops with policy allowlist","updated_at":"2026-03-02T03:34:56Z","waiters":"","wisp_type":"","work_type":""}
{"acceptance_criteria":"","actor":"","agent_state":"","assignee":"Ed_","await_id":"","await_type":"","close_reason":"","closed_at":"2026-03-02T03:04:02Z","closed_by_session":"","compacted_at":null,"compacted_at_commit":null,"compaction_level":0,"content_hash":"911e58b2f36018c64b9bd4fdfec5dc4fffbca208deea583105ff268f501f5fbc","created_at":"2026-03-02T02:14:30Z","created_by":"Ed_","crystallizes":0,"defer_until":null,"description":"Main agent turn loop. Claude Haiku 4.5 primary, Sonnet 4.6 fallback. Tool call dispatch. Asyncio event loop with background daemon thread support.","design":"","due_at":null,"ephemeral":0,"estimated_minutes":null,"event_kind":"","external_ref":null,"hook_bead":"","id":"rook-z5s","is_template":0,"issue_type":"task","last_activity":null,"metadata":"{}","mol_type":"","notes":"","original_size":null,"owner":"edwardgz@gmail.com","payload":"","pinned":0,"priority":2,"quality_score":null,"rig":"","role_bead":"","role_type":"","sender":"","source_repo":"","source_system":"","spec_id":"","status":"closed","target":"","timeout_ns":0,"title":"Core agent loop: asyncio, Claude API, tool dispatch","updated_at":"2026-03-02T03:04:02Z","waiters":"","wisp_type":"","work_type":""}

View File

@@ -146,9 +146,9 @@ After completing a group of related tasks (a "phase"):
- **Language**: Python 3.11+
- **Package manager**: `uv`
- **AI**: Anthropic Claude — `claude-haiku-4-5-20251001` primary, `claude-sonnet-4-6` fallback for complex reasoning
- **Voice In**: Telegram audio notes → STT (OpenClaw-style messaging)
- **Voice Out**: ElevenLabs TTS (Rook voice ID — scratchy/rough robot) with Google TTS as fallback
- **GUI** (ModernCoSy component): Dear PyGui — dockable panels, dark theme
- **Voice In**: Discord audio attachments → STT (`discord.py` bot, `DISCORD_BOT_TOKEN` env var). STT: OpenAI Whisper (`openai`) or Google Cloud STT — pluggable via `STT_BACKEND` env var (`whisper`/`google`)
- **Voice Out**: ElevenLabs TTS (Rook voice ID — `ELEVENLABS_VOICE_ID` env var) with Google Cloud TTS as fallback (`GOOGLE_TTS_KEY` env var)
- **GUI**: `imgui-bundle` (NOT dearpygui — dearpygui has multi-viewport issues). Dockable panels, dark theme.
- **CoSy**: subprocess stdin/stdout pipe to `CoSy.bat` / `reva.exe`
- **Testing**: pytest + pytest-cov

View File

@@ -5,8 +5,10 @@ requires-python = ">=3.11"
dependencies = [
"anthropic",
"elevenlabs",
"python-telegram-bot",
"dearpygui",
"discord.py",
"imgui-bundle",
"openai",
"google-cloud-texttospeech",
]
[dependency-groups]

39
src/rook/cosy.py Normal file
View File

@@ -0,0 +1,39 @@
import subprocess
from rook.policy import is_approved_dir, backup_before_edit
class PolicyError(Exception):
pass
class CoSyProcess:
def __init__(self, cosy_bat: str = 'CoSy.bat') -> None:
self._bat = cosy_bat
self._proc = None
def start(self) -> None:
self._proc = subprocess.Popen(
['cmd', '/c', self._bat],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
)
def send(self, expr: str) -> str:
self._proc.stdin.write(expr + '\n')
self._proc.stdin.flush()
return self._proc.stdout.readline().rstrip('\n')
def stop(self) -> None:
if self._proc:
self._proc.terminate()
def load_vocab(self, vocab_path: str) -> None:
if not is_approved_dir(vocab_path):
raise PolicyError(f'vocab path not approved: {vocab_path}')
self.send(f'FLOAD {vocab_path}')
def redefine_word(self, name: str, definition: str) -> None:
backup_before_edit(name)
self.send(definition)

50
src/rook/tts.py Normal file
View File

@@ -0,0 +1,50 @@
import os
from typing import Optional
class TTSError(Exception):
pass
async def speak(text: str) -> None:
el_key = os.environ.get('ELEVENLABS_API_KEY')
g_key = os.environ.get('GOOGLE_TTS_KEY')
audio: Optional[bytes] = None
if el_key:
try:
audio = _speak_elevenlabs(text)
except Exception:
if g_key:
audio = _speak_google(text)
elif g_key:
audio = _speak_google(text)
if audio is None:
raise TTSError('No TTS keys configured')
_play_audio(audio)
def _speak_elevenlabs(text: str) -> bytes:
from elevenlabs import ElevenLabs
client = ElevenLabs(api_key=os.environ['ELEVENLABS_API_KEY'])
audio_gen = client.generate(text=text, voice=os.environ.get('ELEVENLABS_VOICE_ID', ''))
return b''.join(audio_gen)
def _speak_google(text: str) -> bytes:
from google.cloud import texttospeech
client = texttospeech.TextToSpeechClient()
response = client.synthesize_speech(
input=texttospeech.SynthesisInput(text=text),
voice=texttospeech.VoiceSelectionParams(
language_code='en-US',
ssml_gender=texttospeech.SsmlVoiceGender.NEUTRAL,
),
audio_config=texttospeech.AudioConfig(
audio_encoding=texttospeech.AudioEncoding.MP3,
),
)
return response.audio_content
def _play_audio(audio_bytes: bytes) -> None:
pass

71
tests/test_cosy.py Normal file
View File

@@ -0,0 +1,71 @@
import subprocess
import pytest
from unittest.mock import patch, MagicMock, call
from rook.cosy import CoSyProcess, PolicyError
def test_start_launches_popen():
with patch('rook.cosy.subprocess.Popen') as mock_popen:
mock_proc = MagicMock()
mock_popen.return_value = mock_proc
cosy = CoSyProcess()
cosy.start()
mock_popen.assert_called_once_with(
['cmd', '/c', 'CoSy.bat'],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
)
def test_send_writes_expr_and_returns_output():
with patch('rook.cosy.subprocess.Popen') as mock_popen:
mock_stdin = MagicMock()
mock_stdout = MagicMock()
mock_stdout.readline.return_value = 'ok\n'
mock_proc = MagicMock()
mock_proc.stdin = mock_stdin
mock_proc.stdout = mock_stdout
mock_popen.return_value = mock_proc
cosy = CoSyProcess()
cosy.start()
result = cosy.send('1 2 +')
mock_stdin.write.assert_called_with('1 2 +\n')
assert result == 'ok'
def test_stop_terminates_process():
with patch('rook.cosy.subprocess.Popen') as mock_popen:
mock_proc = MagicMock()
mock_popen.return_value = mock_proc
cosy = CoSyProcess()
cosy.start()
cosy.stop()
mock_proc.terminate.assert_called_once()
def test_load_vocab_approved():
with patch('rook.cosy.is_approved_dir', return_value=True):
cosy = CoSyProcess()
cosy.send = MagicMock()
cosy.load_vocab('/approved/vocab.fs')
args, _ = cosy.send.call_args
assert 'vocab.fs' in args[0]
def test_load_vocab_unapproved_raises():
with patch('rook.cosy.is_approved_dir', return_value=False):
cosy = CoSyProcess()
with pytest.raises(PolicyError):
cosy.load_vocab('/bad/path.fs')
def test_redefine_word_calls_backup():
with patch('rook.cosy.backup_before_edit') as mock_backup:
cosy = CoSyProcess()
cosy.send = MagicMock()
cosy.redefine_word('myword', ': myword 42 ;')
mock_backup.assert_called_once()
cosy.send.assert_called_once_with(': myword 42 ;')

90
tests/test_tts.py Normal file
View File

@@ -0,0 +1,90 @@
import asyncio
import inspect
import pytest
from unittest.mock import patch, MagicMock
def test_speak_elevenlabs_called_when_key_set():
from rook.tts import speak
env = {'ELEVENLABS_API_KEY': 'key', 'ELEVENLABS_VOICE_ID': 'vid'}
with patch.dict('os.environ', env, clear=True):
with patch('rook.tts._speak_elevenlabs', return_value=b'audio') as mock_el:
with patch('rook.tts._play_audio'):
asyncio.run(speak('hello'))
mock_el.assert_called_with('hello')
def test_speak_falls_back_to_google_on_elevenlabs_error():
from rook.tts import speak
env = {
'ELEVENLABS_API_KEY': 'key',
'ELEVENLABS_VOICE_ID': 'vid',
'GOOGLE_TTS_KEY': 'gkey',
}
with patch.dict('os.environ', env, clear=True):
with patch('rook.tts._speak_elevenlabs', side_effect=Exception('API error')):
with patch('rook.tts._speak_google', return_value=b'audio') as mock_google:
with patch('rook.tts._play_audio'):
asyncio.run(speak('hello'))
mock_google.assert_called()
def test_speak_google_only_when_no_elevenlabs():
from rook.tts import speak
env = {'GOOGLE_TTS_KEY': 'gkey'}
with patch.dict('os.environ', env, clear=True):
with patch('rook.tts._speak_google', return_value=b'audio') as mock_google:
with patch('rook.tts._play_audio'):
asyncio.run(speak('hi'))
mock_google.assert_called_with('hi')
def test_speak_raises_tts_error_when_no_keys():
from rook.tts import speak, TTSError
with patch.dict('os.environ', {}, clear=True):
with pytest.raises(TTSError):
asyncio.run(speak('hi'))
def test_speak_calls_play_audio():
from rook.tts import speak
env = {'ELEVENLABS_API_KEY': 'key', 'ELEVENLABS_VOICE_ID': 'vid'}
with patch.dict('os.environ', env, clear=True):
with patch('rook.tts._speak_elevenlabs', return_value=b'bytes'):
with patch('rook.tts._play_audio') as mock_play:
asyncio.run(speak('test'))
mock_play.assert_called_with(b'bytes')
def test_speak_is_coroutine():
from rook.tts import speak
assert inspect.iscoroutinefunction(speak)
def test_speak_elevenlabs_internal():
mock_client = MagicMock()
mock_client.generate.return_value = iter([b'chunk1', b'chunk2'])
mock_elevenlabs_cls = MagicMock(return_value=mock_client)
with patch('elevenlabs.ElevenLabs', mock_elevenlabs_cls):
with patch.dict('os.environ', {'ELEVENLABS_API_KEY': 'k', 'ELEVENLABS_VOICE_ID': 'v'}):
from rook.tts import _speak_elevenlabs
result = _speak_elevenlabs('hi')
assert result == b'chunk1chunk2'
def test_speak_google_internal():
mock_response = MagicMock()
mock_response.audio_content = b'speech'
mock_client = MagicMock()
mock_client.synthesize_speech.return_value = mock_response
mock_tts_cls = MagicMock(return_value=mock_client)
with patch('google.cloud.texttospeech.TextToSpeechClient', mock_tts_cls):
from rook.tts import _speak_google
result = _speak_google('hi')
assert result == b'speech'
def test_play_audio_noop():
from rook.tts import _play_audio
result = _play_audio(b'data')
assert result is None

1150
uv.lock generated

File diff suppressed because it is too large Load Diff