Add a generic 'custom' channel plus an --agent-cmd flag so 'dws devapp
robot connect' can forward to ANY headless AI CLI (question appended as
the trailing arg, stdout used as the reply). Onboards tools that aren't
built-in (e.g. LobsterAI) or self-built agents with no code changes:
--agent-cmd forces channel=custom unless --channel is set explicitly,
and auto-detection falls back to custom when DWS_AGENT_CMD is present.
Also (issue #39): print a one-time hint at connect start when neither a
work dir nor a knowledge source is set, pointing at --agent-workdir /
--knowledge-* / --agent-model so the bot can match terminal answer
quality. Quickstart gains matching FAQ entries plus a note that step 3
(robot connect) produces no approval ticket (issue #19).
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).
Users reported the linked bot feels dumb. Root cause: the forwarder was
hard-pinned to a stateless one-shot haiku run from an empty directory. Expose
the trade-offs as command-set flags instead of hardcoding them:
- --agent-memory (default on): per-conversation session resume. First message
of a DingTalk conversation mints a UUID and passes `--session-id`,
follow-ups pass `--resume`, so multi-turn context survives. Only on CLIs
with addressable sessions (claudecode/codebuddy/workbuddy — verified against
their --help); qodercli has --resume but no --session-id, so the qoder
family stays stateless. Broken sessions self-heal: on forward error the
conversation mapping is dropped and the next message starts fresh.
- --agent-model / DWS_AGENT_MODEL: override the channel CLI's model via each
spec's model flag (replaces claudecode's built-in haiku pin in place).
- --agent-workdir / DWS_AGENT_WORKDIR: run the agent from a knowledge
directory for context, instead of the clean temp dir (which stays the
default to keep cold-start inside DingTalk's reply window).
A DWS_AGENT_CMD override disables flag splicing entirely — that argv is
user-owned and we cannot know which flags it accepts. Dry-run now previews
the effective agent tuning (model/workdir/memory) per channel.
Bring the channel-aware Stream linking ("建联") from PR #407 into the devapp
command tree as `dws devapp robot connect`, so the full lifecycle
(create app -> create robot -> link to a local agent) lives under one
`dws devapp` domain instead of a separate `dws connect` command.
- Reuse, not duplicate: provisioning ("建号") stays on devapp's existing
`robot create/submit/result`; this commit ports only the linking half.
- connect_stream.go: the Go-native in-process Stream forwarder + channel
agent registry, ported verbatim from #407 (package helpers, self-contained).
- devapp_connect.go: trimmed channel routing (resolveConnectChannel /
launchConnector / buildConnectPlan / ...) plus the new subcommand. The
standalone `connect` cobra command, `connect start` and `connect bot create`
are intentionally NOT brought over — the capability is consolidated under
`dws devapp robot`.
- Credentials: `--robot-client-id` / `--robot-client-secret` for an existing
robot, or `--unified-app-id` to auto-fetch via devapp's credentials get.
The flags are named `robot-client-*` (not `client-id`/`client-secret`) to
avoid shadowing the global OAuth client-override persistent flags.
- Adds the dingtalk-stream-sdk-go v0.9.1 dependency.
- Skill docs (devapp.md, robot.md mono+multi) document `robot connect`.
Known follow-up (marked in code + docs): the get_open_dev_app_credentials
response field names used by `--unified-app-id` auto-fetch are not yet
confirmed against the real gateway; the path degrades safely (empty -> fall
back to explicit flags) and must be verified against pre-prod before relying
on it.