Commit Graph
1135 Commits
Author SHA1 Message Date
修雨 79bab762bf feat(connect): private owner approval, self auto-approve, online-sheet audit
Builds on the text-approval gate with the digital-twin model the owner asked
for: the approval stays strictly between the bot and its owner, and every
action is auditable in DingTalk.

- private owner DM: an action request is no longer posted into the requester's
  conversation; the bot DMs the OWNER privately (robot oToMessages/batchSend,
  reusing the bot's own credentials) and the requester sees nothing until the
  result. Decision matching moved from per-conversation to latestPending, since
  the owner replies from their own 1:1 chat, not the request conversation.
- owner self auto-approve: when the requester IS the owner, asking is the
  authorization — the action runs immediately with no second confirmation, but
  is still recorded (auto_approved=true, decided_by=owner) for audit.
- online-sheet audit: optional --audit-sheet / --audit-sheet-tab appends one
  row per terminal action (time, summary, requester, state, decided_by, auto,
  error, id) to a DingTalk online sheet via sheet append_rows, reviewable in
  DingTalk on any device — beyond the local approvals JSON.
- aiCardClient.sendOTOText: proactive 1:1 robot text send primitive.
- requester gets the final outcome (executed/failed/rejected) by 1:1 message;
  the approval process itself is never exposed to them.

Tests: parseDecisionWord, latestPending, private-DM approve/reject,
owner self auto-approve, audit terminal records, classifier wiring intact.
2026-06-15 14:47:02 +08:00
修雨 e840cac3cc Merge pull request #26 from PeterGuy326/feat/connect-text-approval
feat(connect): text approval fallback — owner confirms in chat, no card needed
2026-06-14 11:08:40 +08:00
修雨 45b4f088af feat(connect): text approval fallback — owner confirms in chat, no card needed
The confirmation gate previously REQUIRED a hand-built interactive card
template (per app, manual button param binding in the card console) — and
without one the gate silently turned off. That manual step is too clunky to
be the default. Add a text-approval mode so the digital twin works with zero
card-platform setup: when --owner-user-id is set but no --approval-card-
template, the bot posts a plain confirmation and the owner replies 「同意」/
「拒绝」 in chat to decide.

- newTextApprovalOrchestrator: gate enabled on owner alone (no card sender)
- text mode is async, NOT blocking: handleReply posts the prompt and returns;
  the owner's decision arrives as an ordinary inbound message captured by
  handleOwnerDecision (blocking on Await would deadlock the per-conversation
  worker that must also process that reply)
- gate.pendingForConv maps an owner reply (carries only the conversation) back
  to its request; parseDecisionWord matches a WHOLE-message keyword so an
  embedded 「同意」 never accidentally approves
- only the configured owner can decide; non-owner / non-keyword pass through
- devapp_connect: no card template now falls back to text mode instead of
  disabling the gate
- existing card flow refactored into handleReplyCard, unchanged behavior
- tests: parseDecisionWord, pendingForConv, text-mode approve/reject,
  non-owner rejected, non-decision pass-through
2026-06-14 11:05:13 +08:00
修雨 b28ca6364e Merge pull request #25 from PeterGuy326/feat/connect-role-runtime
feat(connect): wire role config into the connector runtime
2026-06-14 10:45:10 +08:00
修雨 8b39dac5db feat(connect): wire role config into the connector runtime
Sprint 2 item 2: a digital-employee role config (connect_role.go) was a
defined-but-unused schema. Wire it into the connector so a single
--role-config <file> supplies the bot's owner, persona and knowledge
sources instead of scattering them across flags.

- add --role-config / DWS_ROLE_CONFIG; load + validate in launchConnector
  with a client_id-must-match-this-bot guard (one role == one bot)
- applyRoleConfig merges role -> options with explicit-flag-wins semantics
  (a flag/env always overrides the role; knowledge sources are additive)
- owner feeds the approval gate; persona is prepended to every forwarded
  prompt; role knowledge sources load and merge into the same retriever
- AllowedScopes / ConfirmPolicy are parsed and logged but not yet enforced
  (scope gating + auto/remember strategies are a later slice; no dead opts
  fields carried for them)
- tests: applyRoleConfig flag-wins / fill-empty / nil-role

Scope enforcement and confirm-policy behavior intentionally deferred.
2026-06-14 10:41:37 +08:00
修雨 7ecfe85696 Merge pull request #24 from PeterGuy326/feat/connect-gate-classifier
feat(connect): gate engages via read/write classifier, not every marker
2026-06-14 10:27:16 +08:00
修雨 9af7f9a034 feat(connect): gate engages via read/write classifier, not every marker
Sprint 2 item 1: wire the dws command read/write classifier into the
digital-twin approval gate. Previously any detected [[ACTION:...]] marker
was unconditionally routed through the owner-confirmation gate. Now the
gate consults ClassifyDwsCommand on the planned command: a read-class
action bypasses the gate and replies normally, while write — and Unknown,
per the CmdClass safety contract — keep the owner sign-off requirement.

- add classifyPlannedAction bridging plannedAction -> CmdClass (classifies
  on LegacyPath, falls back to product + RPC tool verb)
- handleReply skips Submit/card/Await for a read-class action
- tests: classifyPlannedAction table + handleReply read-class bypass
  (override-driven, exercises the wiring end to end)
2026-06-14 10:22:01 +08:00
修雨 c50494775c Merge pull request #23 from PeterGuy326/feat/connect-cmd-classify
feat(connect): classify dws commands as read vs write for approval gating
2026-06-14 00:12:04 +08:00
修雨 1fe4842525 Merge pull request #22 from PeterGuy326/feat/connect-role-config
feat(connect): role config schema for per-role digital personas
2026-06-14 00:11:57 +08:00
修雨 5708e1c2d2 Merge pull request #21 from PeterGuy326/feat/connect-approval-gate
feat(connect): approval gate — execution requests need owner sign-off before running
2026-06-14 00:11:51 +08:00
修雨 d43ec228aa feat(connect): digital-twin confirmation gate vertical slice
Add the M2 confirmation gate (digital twin): an action request from a
group member is not executed directly. The bot sends an [Approve]/[Reject]
card to the owner over the existing Stream long-connection and only runs the
planned command after the owner approves.

- connect_approval.go: gate engine. ApprovalRequest model, Pending to
  Approved/Rejected to Executed/Failed state machine, thread-safe store with
  crash-safe atomic on-disk persistence (~/.dws/connect/<clientId>/approvals,
  0600, restart-recoverable), Submit/Decide/Await/Get API, and the simplified
  [[ACTION:...]] marker detection for execution-class requests.
- connect_approval_card.go: approvalCardSender boundary (DI-fakeable), the
  card-callback decoder (approval id + decision from action params, OutTrackId
  fallback), the orchestrator driving Submit to card to Await to execute to
  reply, and the real DingTalk interactive-card sender.
- connect_stream.go / devapp_connect.go: minimal wiring. Register the card
  callback router, decorate the prompt for action detection, route a marked
  reply through the gate, --owner-user-id / --approval-card-template flags.

Tests: state machine + persistence/recovery, card-action decode, reject path
(no execution), and a no-network end-to-end approve chain. gofmt/vet/build/
test all green.
2026-06-13 23:34:37 +08:00
修雨 fb335fb403 feat(connect): add dws command read/write classifier
Add ClassifyDwsCommand, a standalone heuristic classifier that labels a
dws command (by path segments) as read / write / unknown. It is the
signal a connector confirmation gate consumes to decide whether a robot
may run a command directly (read-only) or must first ask the principal
to approve it (write / mutating).

- Read/write verb tables calibrated against real dws cobra Use: verbs
  scanned from the repo (list/get/search/status/export/download vs
  create/update/delete/send/done/upload/recall/mkdir/chmod ...).
- Segments scanned right-to-left so the leaf action verb dominates a
  container/placeholder segment; falls back toward the root when the
  leaf is unrecognised (e.g. a trailing id).
- Compound leaf verbs normalised by their leading token before the
  first '-'/'_' (send-by-bot, list-forms, batch-update, add-bot ...).
- Override mechanism: per-call map (ClassifyDwsCommandWith) and a
  process-wide table (SetCmdClassOverride), keyed by single verb or
  full space-joined path, full-path winning. Overrides beat heuristics.
- Unknown is returned losslessly; the CmdClass doc comment documents the
  safety contract that callers must treat Unknown conservatively as
  write (require confirmation), never auto-allow.

This is the classifier component only; wiring it into the confirmation
gate is a separate change.
2026-06-13 23:25:04 +08:00
修雨 fd61526707 feat(connect): add digital-employee role config schema and loader
Introduce internal/helpers/connect_role.go: a standalone configuration
component for the one-role-per-bot digital-employee model. Each role binds
to a single bot via client_id and carries its persona, knowledge sources,
allowed capability scopes, owner userId and confirmation policy.

- RoleConfig struct (YAML) with manual/auto/remember confirm policy enum.
- LoadRoleConfig: read + parse + validate with clear, non-panicking errors
  (required name/client_id/owner_user_id, policy enum, source grammar).
- knowledge_sources reuse the existing parseKnowledgeSource grammar so the
  schema stays in lockstep with --knowledge-source (path / wiki: / doc:).
- LoadRoleConfigs: index roles by client_id for multi-bot deployments,
  rejecting duplicate client_id.
- RoleConfigExample documents a full role and is asserted in tests.

Schema only; no runtime wiring.
2026-06-13 23:21:16 +08:00
修雨 c858241b68 Merge pull request #20 from PeterGuy326/fix/forwarder-test-arity
fix(connect): repair forwarderForChannel test arity after #15/#16 merge
2026-06-13 23:07:09 +08:00
修雨 b826440995 fix(connect): update provider-model test for forwarderForChannel clientID arg
PR #15's connect_provider_model_test.go calls forwarderForChannel with the
old 2-arg signature; PR #16 added a clientID parameter. Both branches passed
in isolation but the merged release broke 'go test ./internal/helpers/'.
Pass an empty clientID (the test exercises model-arg injection, unrelated to
session persistence).
2026-06-13 23:03:12 +08:00
修雨 25f69b867e Merge pull request #18 from PeterGuy326/feat/connect-knowledge-wiki
feat(connect): mount DingTalk wiki space as a knowledge source
2026-06-13 22:58:23 +08:00
修雨 172060b868 Merge pull request #17 from PeterGuy326/feat/connect-daemon
feat(connect): daemonize the connector with crash-restart, status and stop
2026-06-13 22:58:17 +08:00
修雨 dd0990691e Merge pull request #16 from PeterGuy326/feat/connect-session-persist
feat(connect): persist conversation sessions across connector restarts
2026-06-13 22:58:11 +08:00
修雨 02bc7ce880 Merge pull request #15 from PeterGuy326/fix/reply-card-422-fallback
fix(connect): stop echoing provider 422 into chat; drop haiku pin for custom providers
2026-06-13 22:58:04 +08:00
修雨 4d476ed317 style: gofmt wiki knowledge source sources 2026-06-13 22:23:58 +08:00
修雨 ff89ffed69 style: gofmt connect_daemon sources 2026-06-13 22:23:48 +08:00
修雨 65ab1a3076 feat(connect): add daemon mode with crash-restart, status and stop
Turn `dws devapp robot connect` into a 7x24 background service.

- `connect --daemon` re-execs dws into a detached supervisor (POSIX setsid),
  prints pid + log path, and exits. Windows is unsupported (stub errors out).
- A supervisor process keeps a worker connector alive, restarting it with
  exponential backoff (1s..60s cap, 10 consecutive fast-failure ceiling).
- `connect status` reports running/stale/not-running, pid, uptime and log path.
- `connect stop` sends SIGTERM (worker releases the single-instance lock and
  Stream connection), escalates to SIGKILL on a timeout, and cleans the pid file.
- stdout/stderr are archived to ~/.dws/connect/<clientId>/daemon.log via the
  existing size-based rotator (new additive logging.NewRotatingFile).
- docs/connect-daemon-service.md ships launchd and systemd templates for
  boot-time auto-start.

The single-instance lock (connect_lock.go) is reused unchanged. The foreground
forwarding/session/knowledge logic is untouched; devapp_connect.go only gains
the --daemon flag, two hidden re-exec flags, and the status/stop subcommands.
2026-06-13 21:44:47 +08:00
修雨 cf277f3c8f feat(connect): add wiki/doc knowledge source for the Q&A connector
Extend the connector's knowledge retrieval (previously local .md/.txt only)
to a DingTalk knowledge base via --knowledge-source wiki:<spaceId> (and
doc:<docId>). On startup the connector pulls the space by reusing the existing
`doc` tools that back `dws doc list` / `dws doc read` (list_nodes +
get_document_content) through the shared executor.Runner -- no new DingTalk API
call is implemented. Pulled documents are dumped as markdown into a local cache
(~/.dws/connect/<clientId>/knowledge/wiki-<spaceId>/) and indexed by the same
chunker/retriever as the local-directory source.

Refresh on startup; on a pull failure (network/permission) fall back to a stale
cache if present, otherwise an empty knowledge base -- the connection is never
blocked. The legacy --knowledge-dir source is untouched and coexists.

Node listing uses a shallow id lookup so a response wrapper object is not
mistaken for a single node (which would drop sibling nodes).
2026-06-13 21:43:48 +08:00
修雨 59100407dc feat(connect): persist conversation sessions across connector restarts
convSessions held the DingTalk-conversation to agent-session map purely
in memory, so a connector restart dropped every chat's multi-turn
context - the core weakness of an always-on digital employee.

Persist the map to ~/.dws/connect/<clientId>/sessions.json (scoped per
clientId so multiple bots on one machine stay isolated), reusing the
existing config dir, 0600 FilePerm and lock-id sanitizer conventions:

- newConvSessions restores the map on startup; a missing or corrupt
  file degrades to an empty map with a warning, never a panic or a
  blocked connection.
- args/reset persist after every mutation, under the existing mutex so
  the snapshot is race-free, via a temp-file + rename atomic write.
- saves are best-effort: a write failure only logs a warning and never
  blocks message handling.
- an empty path (clientId missing or --agent-memory off) keeps the map
  in memory only, preserving the original behaviour exactly.

forwarderForChannel now takes clientId to derive the per-bot store path.
Adds unit tests for reload, corrupt-file degradation, in-memory mode and
concurrent access (race-clean).
2026-06-13 21:39:45 +08:00
修雨 10de987e81 fix(connect): stop echoing provider 422 into chat; drop haiku pin for custom providers
issue #14: the "422" seen in the group was not from the DingTalk card API but
from the claude subprocess's model provider, echoed back as the reply.

Root cause:
- claudecode's spec pins --model claude-haiku-4-5-20251001 in both argvTail and
  streamArgvTail.
- claudeUserSettingsEnv (issue #10) injects the user's ANTHROPIC_BASE_URL /
  ANTHROPIC_AUTH_TOKEN into the claude subprocess. With a third-party provider
  relay that has no mapping for that exact haiku pin, the provider returns
  "API Error: 422 ...".
- execForwarder.forward used cmd.Output() and returned any non-empty stdout as
  the answer without checking err, so the raw 422 text was forwarded to the chat
  on every message.

Fix (A + B):
A. In forwarderForChannel, when a custom provider base URL is in effect
   (injected via Claude settings or already in the process env) and the user did
   not pick a model (--agent-model / DWS_AGENT_MODEL), strip the built-in model
   pin so the provider's default model applies. Official-login users keep the
   pin; an explicit --agent-model still wins.
B. In forward and forwardStream, treat stdout that starts with "API Error:"
   (or a process error) as a failure: return a short, actionable Chinese hint
   ("...请用 --agent-model <model> 指定模型后重连.") instead of echoing the raw
   backend error. Normal replies are unaffected.

Adds unit tests for the haiku-drop wiring, the API-Error guard, and the new
helpers.
2026-06-13 14:43:24 +08:00
修雨 c36fcaaf70 Merge pull request #13 from PeterGuy326/fix/quickstart-paste-safe-placeholders
docs(quickstart): paste-safe placeholders for the connect example
2026-06-12 18:05:55 +08:00
修雨 0cf97cea55 Merge pull request #12 from PeterGuy326/fix/skill-setup-embedded-source
fix(skill): embed skill sources in binary + cover qoder dirs in upgrade refresh
2026-06-12 18:05:49 +08:00
修雨 47354b918a docs(quickstart): make the connect example paste-safe in zsh
The step-3 example used <第二步的clientId> style placeholders; pasting
them verbatim makes zsh treat the angle brackets as redirections and
fail with "parse error near \n" before the CLI even runs (issue
PeterGuy326#11). Use realistic dummy values plus an explicit replace-me
note instead, and add a FAQ entry for anyone still hitting the error
from the old wording. Also bump install snippets to v1.0.53-dws-devapp.
2026-06-12 18:00:56 +08:00
修雨 df1e33442b fix(skill): embed skill sources in the binary and cover qoder dirs in upgrade refresh
Two distribution defects left installed skills stale (issue PeterGuy326#8):

1. skill setup resolved its source from cwd / exe-adjacent checkouts, so
   an upgraded binary happily re-installed whatever stale checkout it was
   run from, and agents kept routing 'create robot' into the chat dead end.
   The skills/mono and skills/multi trees are now embedded (go:embed) and
   setup defaults to that copy, materialized into ~/.dws/skills/<mode> —
   upgrading the binary upgrades what setup installs. --source and
   DWS_SKILL_SOURCE stay as explicit overrides, and an override that has
   no skill root errors out instead of silently falling back.

2. dws upgrade's skill refresh (knownSkillDirs) skipped ~/.qoder and
   ~/.qoderwork, so Qoder/QoderWork hosts never got refreshed routing.
   Added both to the dir table and its seven mirrors (npm install.js,
   install.sh/ps1, install-skills.sh, homebrew template, package test,
   verify-package-managers.sh).

setup-side detection and --target qoder/qoderwork already work on this
branch; this closes the remaining upgrade-side and source-side gaps.
2026-06-12 17:57:48 +08:00
修雨 a7ca1e1a5b fix(connect): pass user-level Claude settings env through to the claudecode channel
The claudecode channel spawns claude with --setting-sources project to
keep the bot persona neutral, but that also drops the user-level
settings.json env block where third-party provider tools (cc-switch
etc.) store ANTHROPIC_BASE_URL / ANTHROPIC_AUTH_TOKEN. claude then
fell back to the official login and replied "Not logged in - Please
run /login" for anyone on a relayed model.

Re-expose that env block as process environment for the spawned CLI
(both one-shot and streaming paths) via the channel envFn hook.
Variables already exported by the operator are not overridden, and
user hooks/plugins still stay out of replies.

Fixes PeterGuy326#10.
2026-06-12 16:14:50 +08:00
修雨 211a06c05a fix(devapp): resolve op-app MCP endpoint from gateway base URL
The devapp endpoint was hardcoded to the pre gateway, so robot
create/list hit the pre environment for everyone. Derive it from the
configured MCP gateway base URL instead: production by default, pre
when ~/.dws/mcp_url points at the pre gateway.

Also bump quickstart doc to v1.0.51-dws-devapp and add a FAQ entry for
the production-side developer-identity check.
2026-06-12 14:54:05 +08:00
修雨 bb52a505dc docs: add customer-facing robot quickstart guide (install, create, connect, add to group) 2026-06-12 14:20:26 +08:00
修雨 f899f4f6b6 chore(release): target PeterGuy326 fork for goreleaser, guard npm publish to official repo 2026-06-12 12:04:22 +08:00
wxianfeng 3adeffb09d event subscribe 2026-06-12 11:33:55 +08:00
xianfeng wang 435bf3151c Merge pull request #9 from PeterGuy326/feat/connect-qa-hardening
feat(connect): Q&A-bot hardening — picture messages, knowledge retrieval, allowlist/rate-limit, per-chat FIFO, single-instance lock
2026-06-12 10:02:47 +08:00
修雨 1c90edb92a fix(connect): drain buffered app-server frames before honoring read EOF
The codex app-server read loop reports EOF before closing the message
channel, so when the process exits right after its final frame, both
channels are ready and select picks one at random — runTurn could drop
an already-received turn/completed and fail with "stream ended before
turn/completed: EOF" (the CI flake on this PR and #8). Drain buffered
frames first; reproduced at ~7%/300 runs before, 0/300 after.
2026-06-12 09:42:51 +08:00
修雨 36b89a04b1 Merge remote-tracking branch 'wxianfeng/feat/dws-devapp' into HEAD
# Conflicts:
#	internal/helpers/connect_stream.go
2026-06-12 09:18:10 +08:00
修雨 cdd8414891 docs: cut 1.0.37 changelog (#457) v1.0.37 2026-06-11 19:53:26 +08:00
Aemeath 9d3980f90a Merge pull request #8 from wxianfeng/codex/codex-channel-lab
[codex] add Codex app-server robot bridge
2026-06-11 19:49:11 +08:00
quanming ae5ef70bb8 fix: harden codex app-server client concurrency 2026-06-11 19:27:50 +08:00
xuanandshangguanxuan.sgx 2a82d07311 fix(pat): support batch agentCode and guarded grants (#455)
* fix(pat): carry agentCode in batch auth args

* fix(pat): let core default missing agent code

* fix(pat): require yes for batch grants

* test(pat): cover cli authorization matrix

* fix(pat): keep canonical agent code env only

---------

Co-authored-by: shangguanxuan.sgx <shangguanxuan.sgx@alibaba-inc.com>
2026-06-11 19:21:25 +08:00
玉澜 89c3aba2ed docs(devapp): capture publish workflow edge cases 2026-06-11 13:09:27 +08:00
修雨 c151756168 feat(connect): Q&A-bot hardening for the stream bridge
Five operational gaps that bite when a connector-backed bot answers
questions in real groups:

- Single-instance lock: one connector per robot per machine (pid file,
  stale-lock takeover). Duplicate Stream connections on one clientId get
  messages load-balanced between them, so the bot answers intermittently
  (verified live).
- Access policy: --allowed-users / --allowed-groups allowlists plus a
  per-sender rate limit (--user-rate-limit, default 20/min). Every
  message is an LLM call; without this anyone who can reach the bot can
  spend on its behalf.
- Per-conversation FIFO queue: same-chat messages run in arrival order
  (follow-ups need the previous turn's --resume session; parallel CLIs
  racing one session corrupt it); different chats stay parallel.
- Picture messages: resolve the callback downloadCode via robot
  messageFiles/download to a local temp file and hand the path to the
  agent CLI — error screenshots are the top Q&A inbound and were
  previously dropped silently. The claudecode persona now permits
  reading attached files.
- --knowledge-dir: lightweight local retrieval (markdown chunking,
  latin-token + CJK-bigram scoring) prepends top-k matching chunks to
  each question while the agent keeps running from the clean scratch
  dir, so replies stay inside DingTalk's AI-assistant response window.

All knobs have env mirrors (DWS_ALLOWED_USERS / DWS_ALLOWED_GROUPS /
DWS_USER_RATE_LIMIT / DWS_KNOWLEDGE_DIR) for service-style runs.

go build ./... clean; go test ./internal/helpers -race passes with new
tests for lock/gate/queue/knowledge/media. test/cli_compat, test/scripts
and test/unit fail identically on the unmodified base (pre-existing
fixture issues).
2026-06-11 12:18:25 +08:00
玉澜 473d2f9aaf docs(devapp): document status handling 2026-06-11 12:00:27 +08:00
quanming 3dcb40c634 feat: add codex app-server robot bridge 2026-06-11 11:10:25 +08:00
玉澜 5e4b5e4eda docs(devapp): add one-click install guide 2026-06-11 10:26:17 +08:00
修雨 60ac0b409d fix(cli): guard canonical mcp tree against poisoned-cache flag collisions (#454)
* fix(cli): guard canonical mcp tree against poisoned-cache flag collisions

The canonical 'dws mcp' tree is built from cached catalog data before the
legacy command build and before Cobra dispatches anything, so a pflag
panic there (a tool schema property named after the reserved --params
flag, as cached during the 1.0.32 incident) aborted every invocation --
including 'dws cache refresh' and 'dws upgrade' -- and sat outside all
three poisoned-cache guards (#447/#449/#452).

Two layers, mirroring the existing guards:
- applyFlagSpecs now skips reserved (--json/--params), duplicate and
  alias-colliding flag names and sanitizes shorthands instead of letting
  pflag panic; the skipped property stays reachable through the reserved
  JSON payload flags (same degradation semantics as #449).
- newMCPCommand wraps the build in the #452 recover -> quarantine ->
  retry-once -> degrade-to-stub sequence, so even an unforeseen panic
  class no longer locks the CLI out.

* docs: amend 1.0.36 changelog for the re-cut with the canonical tree guard (#454)
v1.0.36
2026-06-11 09:16:36 +08:00
john d873b9c017 Merge pull request #7 from PeterGuy326/fix/connect-card-doc-hermes-guidance
docs(connect): fix stale CardTemplate comment + document AI-assistant reply window for hermes channel
2026-06-11 09:13:19 +08:00
修雨 4b8acc8e73 docs(connect): fix stale CardTemplate comment; document AI-assistant reply window in hermes channel guidance
- ConnectOptions.CardTemplate said an empty value falls back to the public
  openclaw template, but the code (newAICardClient/hasTemplate) treats empty
  as cards-off, and the explicit "public" alias exists precisely for that
  opt-in. Align the comment with the actual contract.
- hermes official-channel steps now warn about the trap verified live: a
  devapp robot renders replies through the AI-assistant response window, so
  a slow agent's plain-text reply gets the Done reaction (HTTP 200) but never
  shows up, leaving the loading card stuck. Point users to enabling AI cards
  on the hermes side.
2026-06-10 23:54:15 +08:00
修雨 4bc4b60dca docs: cut 1.0.36 changelog (#453) 2026-06-10 22:45:50 +08:00