ohmo init rewrites gateway.json without a domain field, undoing the
larksuite.com fix on every reconfigure. Add a domain selector so the
choice is persisted through init.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(memory): add structured schema and usage index
Add schema-v1 frontmatter for memory files, including stable ids, deterministic signatures, soft delete metadata, TTL handling, and migration support for legacy stores.
Track recalled memories in usage_index.json so retrieval can prioritize useful memories and auto-dream can review stale unused entries before pruning.
Keep project and ohmo memory backends aligned under the same behavior while preserving runtime compatibility for unmigrated Markdown files.
* fix(memory): make backend migration defaults explicit
Add auto-dream memory consolidation with lock-based scheduling, manual /dream commands, backup/diff/rollback support, and ohmo integration for project and personal memory.
Add a configurable image_generation tool with OpenAI-compatible and Codex hosted providers, propagate generated media metadata through stream events, and let ohmo channels send generated image/file paths automatically.
Add an ohmo-only /group flow for Feishu private chats, persist managed group metadata, and route shared-chat sessions safely. Also update Feishu reply handling and gateway tests for private/group behavior.
The ohmo gateway process management (start, find, stop) only worked on
Unix-like systems. On Windows, all three operations fail:
- start_gateway_process: uses start_new_session=True (no equivalent on
Windows; also lacks stdin=DEVNULL which causes the subprocess to
inherit the parent's console input)
- _pid_is_running: uses os.kill(pid, 0) which raises PermissionError
for processes the caller doesn't own on Windows
- _iter_workspace_gateway_pids: shells out to `ps`, which doesn't exist
on Windows
- stop_gateway_process: uses os.kill(pid, SIGTERM), which is not
supported on Windows
Replace each with a Windows-compatible path guarded by sys.platform:
- Start: CREATE_NEW_PROCESS_GROUP | DETACHED_PROCESS, stdin=DEVNULL
- PID check: OpenProcess/GetExitCodeProcess via ctypes (STILL_ACTIVE)
- PID listing: wmic process where commandline like ... get processid
- Stop: taskkill /F /T /PID
The Unix paths remain unchanged.
Co-authored-by: ancietyding <ancietyding@tencent.com>
Support absolute glob patterns without crashing and retry ohmo channel messages without ImageBlocks when a provider rejects image input.\n\nFixes #225\nFixes #226
The Feishu channel did not support replying inside group topic threads.
Every bot response created a new top-level message, making conversations
in topic-enabled groups fragmented.
Three changes:
1. feishu.py: include thread_id/root_id in inbound metadata so that
router.py can create per-topic session keys for group chats.
2. feishu.py: add reply_in_thread support to _send_message_sync using
the lark-oapi ReplyMessageRequest with .reply_in_thread(True).
3. bridge.py: forward inbound message_id to OutboundMessage metadata
so the Feishu channel can use it for thread replies.
Files under ~/.openharness/ — credentials, settings, session snapshots,
cron registry, memory index — were written with `Path.write_text()` in
truncating mode. A crash, SIGKILL, power loss, or out-of-disk error
during the write leaves a truncated file on disk; concurrent writers
clobber each other's updates; and the credentials file spent a brief
window at the default umask mode (commonly 0o644) before chmod-to-0600
ran.
Introduce `openharness.utils.fs.atomic_write_text` / `atomic_write_bytes`
which write to a same-directory temp file, fsync, apply the target mode
while the file is still private, and `os.replace` into place. Thread
them through all persistence writers. For read-modify-write on shared
files (credentials, settings, cron, memory index), pair atomic writes
with the existing `exclusive_file_lock` primitive so two `oh` processes
no longer race.
The generic lock helper moves from `openharness.swarm.lockfile` to
`openharness.utils.file_lock`. `swarm.lockfile` is retained as a thin
re-export so existing callers keep working.
Co-authored-by: José Maia <glitch-ux@users.noreply.github.com>