Compare commits
5 Commits
0120bd8b1b
...
83dd16a6c2
| Author | SHA1 | Date | |
|---|---|---|---|
| 83dd16a6c2 | |||
| 84e04dfa7a | |||
| 2df984ac4c | |||
| a80b31f1d9 | |||
| 2344f7cff5 |
@@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"last_dolt_commit": "toe8dna9743n523thnefrp1ddnu8blag",
|
"last_dolt_commit": "6bdv5ku8o7amuah7crbk8vnf4tmai8m6",
|
||||||
"last_event_id": 0,
|
"last_event_id": 0,
|
||||||
"timestamp": "2026-03-02T03:34:56.2779025Z",
|
"timestamp": "2026-03-02T03:52:13.6279948Z",
|
||||||
"counts": {
|
"counts": {
|
||||||
"issues": 10,
|
"issues": 10,
|
||||||
"events": 22,
|
"events": 29,
|
||||||
"comments": 0,
|
"comments": 0,
|
||||||
"dependencies": 14,
|
"dependencies": 14,
|
||||||
"labels": 0,
|
"labels": 0,
|
||||||
|
|||||||
@@ -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: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: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: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\"}"}
|
||||||
|
|||||||
@@ -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: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":"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-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":"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":"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":"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":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: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":""}
|
{"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":""}
|
||||||
|
|||||||
@@ -146,9 +146,9 @@ After completing a group of related tasks (a "phase"):
|
|||||||
- **Language**: Python 3.11+
|
- **Language**: Python 3.11+
|
||||||
- **Package manager**: `uv`
|
- **Package manager**: `uv`
|
||||||
- **AI**: Anthropic Claude — `claude-haiku-4-5-20251001` primary, `claude-sonnet-4-6` fallback for complex reasoning
|
- **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 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 — scratchy/rough robot) with Google TTS as fallback
|
- **Voice Out**: ElevenLabs TTS (Rook voice ID — `ELEVENLABS_VOICE_ID` env var) with Google Cloud TTS as fallback (`GOOGLE_TTS_KEY` env var)
|
||||||
- **GUI** (ModernCoSy component): Dear PyGui — dockable panels, dark theme
|
- **GUI**: `imgui-bundle` (NOT dearpygui — dearpygui has multi-viewport issues). Dockable panels, dark theme.
|
||||||
- **CoSy**: subprocess stdin/stdout pipe to `CoSy.bat` / `reva.exe`
|
- **CoSy**: subprocess stdin/stdout pipe to `CoSy.bat` / `reva.exe`
|
||||||
- **Testing**: pytest + pytest-cov
|
- **Testing**: pytest + pytest-cov
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,10 @@ requires-python = ">=3.11"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"anthropic",
|
"anthropic",
|
||||||
"elevenlabs",
|
"elevenlabs",
|
||||||
"python-telegram-bot",
|
"discord.py",
|
||||||
"dearpygui",
|
"imgui-bundle",
|
||||||
|
"openai",
|
||||||
|
"google-cloud-texttospeech",
|
||||||
]
|
]
|
||||||
|
|
||||||
[dependency-groups]
|
[dependency-groups]
|
||||||
|
|||||||
39
src/rook/cosy.py
Normal file
39
src/rook/cosy.py
Normal 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
50
src/rook/tts.py
Normal 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
71
tests/test_cosy.py
Normal 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
90
tests/test_tts.py
Normal 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
|
||||||
Reference in New Issue
Block a user