Skip to content

Releases: IvanWu2015/dsh-connect

dsh-connect 1.0.17

Choose a tag to compare

@IvanWu2015 IvanWu2015 released this 28 Oct 09:51

[1.0.17] - 2026-10-28

Nine fixes, most of them for behaviour that only shows up when a question goes unanswered or a
turn runs long. Several of these are corrections to 1.0.16 — including one where the earlier
fix caused the next problem.

Fixed

  • A desktop question was delivered to Feishu. Reported twice as 「我直接在PC端上发内容,但提问的按钮…去到了飞书」, and it took two attempts. A question only reaches this package through a channel adapter, and a runner exists only when a message arrives that way — a turn started in the desktop Web GUI never touches dsh-connect, so no runner was driving it. The old code then fell back to binding order, which is Feishu. Preferring a "busy" surface could never help: the WebAdapter declares supportsChoices = false and its outbound methods are no-ops by design, so it is never even a candidate. A question is now claimed only when some channel is driving the turn right now; everything else is handed back to the host, which is what lets the GUI render its own question UI. Claiming is now the exception, not the rule.
  • An expired question card never came back. Reported as 「菜单过期,不会重新刷新,一段时间回来后,卡在那里」 — caused by the 1.0.16 anti-flood change. Capping redraws at five and then setting a flag that never cleared stopped the flood and killed the question with it. The cap is now a cooldown that suppresses only the repaint: the listener stays armed, so a returning user's tap is heard, and any tap clears the cooldown so the card redraws immediately. "Do not redraw" and "do not listen" were conflated; they are now separate.
  • A turn reported only its last assistant message as the result. Reported as 「好像没处理完就结束了」 with a card ending mid-sentence. The turn had 67 steps and 45 assistant messages, and summarizeTurn assigned text = joined per message, so each replaced the previous one — the card showed the 26-character final fragment while every earlier conclusion, including the actual answer, was discarded. Text is now accumulated. On the reported turn that is 2,896 characters, inside every channel's limit.
  • A failed reminder rewrite dropped the notice. Introduced by 1.0.16's single-message change and reported as 「过了5分钟后好像都没有了」. The rewrite path ended in a .catch that cleared the id and returned, so a failed edit silently discarded the report the user had configured. A failed rewrite now posts instead, and says so in the log.
  • A question card left live buttons after its redraw failed. The redraw failure now recalls the card as a last resort, so the buttons cannot survive a rewrite the API refuses. The failure still propagates, keeping the retry's chance at a transient refusal.
  • An abandoned card in a question chain was never disarmed. When an in-place update failed mid-chain a fresh card was sent and the old one was left with live buttons. A comment claimed they had "died"; a rejected API call changes nothing. The abandoned card is now rewritten to an expired notice.

Changed

  • A run-away progress reminder now updates one message instead of stacking. Reported from a turn left overnight at 「已进行 820 分钟」: a 13.6-hour turn produced over 160 near-identical bubbles, each restating the elapsed time. The first notice posts; every later one rewrites it. Adapters gain an optional sendTextWithId/updateText pair for this, forwarded through the retry wrapper.
  • An unanswered question slows its redraws instead of stopping. The first expiries redraw at the normal pace; after that the interval widens to 15 minutes, so an unattended night stays quiet without making the question unreachable.

Documentation

  • The repository page now leads with the chat itself, and the settings screenshots were cut from 24 to 18 across the four READMEs. The previous release added the chat screenshots to the package README only — GitHub shows the one at the repository root, which had none.

Testing

  • 643 tests, 31 suites, green, plus E2E OK against both a mock and a live host, re-verified against DSH 0.2.1-rc.3.
  • Every fix was verified to fail against the code it guards first. Two are worth recording:
    • The desktop-routing test does not merely fail with the old fallback — it hangs, because the question is claimed by Feishu and nothing can answer it. That is the reported symptom reproduced exactly.
    • Three tests were written twice because they passed against the unfixed code: they counted presentations where they should have counted repaints, or advanced a counter on a path that does not increment it.
  • The interaction suite also stopped exiting cleanly — a pre-existing hang, reproduced against the previous commit. The loop's delay is now unref'd, the same policy the answer-waiter timer already followed: a question waiting for a human should not keep a process alive.

dsh-connect 1.0.16

Choose a tag to compare

@IvanWu2015 IvanWu2015 released this 17 Oct 13:52

Fixes for things that only misbehave on long runs, plus the screenshots the README never had.

Fixed

Progress reminders stopped after the first one. Reported twice as 「已进行5分钟这个还是没有更新」, and the session transcript finally explained it: the turn ran 55.6 minutes with 54 tool calls and never ended, while the user saw ONE reminder where eleven were due. Feishu closes a streaming card ten minutes after creating it — the Lark SDK states it three times — and the reminder's only channel was that card, so from minute ten onward every notice was appended to a card that no longer existed. The API accepts the edit, the client shows nothing, and nothing is logged. Past the card's lifetime the notice is now its own message.

A failed card close was swallowed, which also disabled its retry. The close ended in .catch(() => undefined), so closeMenu could never fail — and the service wraps that very method in retry(), which therefore never ran. A transient redraw failure left the buttons on screen with nothing in the log.

A question card was re-sent every five seconds while unanswered, because the card was presented inside the five-second wait loop.

A typed answer left the buttons live, because the card's id was only learned when the card itself settled — which typing never causes.

Added

A decomposed task posts its subtask list, then keeps it current — capped at 10 entries, the in-progress step kept even past the cut, and an unchanged list costing no API call. The card states when it was last accurate: measured on a real session, the agent called todo_write only every 78–109 minutes (up to 224 tool calls apart), so a live-looking list could be an hour out of date. It deliberately does not infer progress from tool activity, which would mean inventing a status the agent never reported.

Documentation

The READMEs now show the chat itself, which they never had: question cards with tappable options, a progress reminder advancing 5 → 10 → 15 minutes, and the /settings button menu.


636 tests, 31 suites, all green, plus E2E OK (both legs). Every fix was verified to fail against the code it guards first.

dsh-connect 1.0.15

Choose a tag to compare

@IvanWu2015 IvanWu2015 released this 10 Oct 11:58

Two fixes and a documentation pass.

Fixed

A question card was re-sent every five seconds while it went unanswered. The previous release stopped an unresponsive card from holding a flow open by racing it against a five-second wait for a typed reply — but the card was presented inside that wait's loop, so each elapsed window presented it again. The user saw the same 「需要你的选择(问题 1/2)」 card arrive over and over. The card is now presented once and that promise is kept across iterations, so the wait may time out and re-check for a typed answer as often as it likes while the card on screen stays put.

A progress reminder could be dropped without a word. The watchdog edited the streaming card through the turn's chunk queue, and when the turn state was gone it returned silently — no card edit, no message, no log. disposeAgent clears that state mid-turn without stopping the watchdog. A progress report whose only job is to say "still working" now falls back to posting its own message.

Documentation

  • The READMEs now open with why the plugin exists, not just what it does. The Web GUI is not replaced by this; what it cannot do is reach you.
  • Screenshots moved to the top, with the gap stated. All 14 are of the settings pane and none of the chat itself — said plainly rather than presenting a settings pane as the product.
  • The /help output was half-translated. Eight command lines were hardcoded English; a Chinese user got a mixed-language command list in the one place they go to discover what exists.
  • The one-sided lock is now stated where users meet it. The Web GUI reads the mirrored session straight from DSH's session store, so it cannot respect the chat lock. Running one session from two writers is possible, not prevented.
  • docs/COMPETITIVE_ANALYSIS.md: the landscape, where this plugin stands, and what to build next. Every vendor-integrated competitor verified is Slack-only; none documents Feishu/Lark or DingTalk.

624 tests, 30 suites, all green, plus SMOKE OK and E2E OK (both legs, including the live round trip against a real dsh host). The card-spam regression was caught by a test proven to fail first: reverting the fix makes it report "presented 3 times" in 12 seconds — the reported symptom exactly.

dsh-connect 1.0.14

Choose a tag to compare

@IvanWu2015 IvanWu2015 released this 09 Oct 10:49

Nine releases in one batch (1.0.6 → 1.0.14), all from one review thread. The through-line: several settings were honoured on some code paths and not others, so the UI promised things the runtime did not do.

Notification levels actually quiet (1.0.6–1.0.8)

输出重要节点 and 只输出结果 still streamed the answer, a per-minute heartbeat and a 5-minute progress reminder, because every gate was written as a negated check copied onto each call site — fixing result left important just as noisy. All gates now go through one predicate that admits full alone. Only full narrates; the quieter levels get the answer once, whole, at turn end.

/status no longer fabricates a completion time (1.0.7)

It read the clock instead of the turn/end event's own timestamp, so every report claimed the task had just finished — including tasks that had never finished. It also claimed a running task when nothing was driving one (a turn orphaned by a host restart).

Automatic context compaction (1.0.9)

autoCompact + autoCompactThresholdPct (default 80, clamped 1–99), editable in 通用设置 and per chat via /autocompact. The turn-end card now always reports context usage — including when the window size is unknown — and names the active rule.

Access control is per channel (1.0.11)

allowUsers/allowChats discarded the channel argument outright, so one list could only ever be correct for one channel: a Feishu ou_… open id means nothing to Telegram. Each channel now carries its own, with the top-level keys kept as a fallback.

The question card can no longer wedge (1.0.12–1.0.14)

The one that mattered: askOne awaited promptChoice and nothing else. If a card never resolved, the loop sat inside that single await forever, and the text-reply escape route recorded the answer into a variable nothing read — so a question with options had no way out at all. The card and the text path now race, so replying by text always gets through. Also: long option labels no longer render truncated in a 2-column grid, an unusable tap says so instead of looping in silence, and a card is closed once every question is answered.


623 tests, 30 suites, all green, plus SMOKE OK and E2E OK (both legs, including the live one). Every fix was verified to go red against the code it guards. Two regression tests had to be corrected first because they encoded the bug they were meant to catch, and one was passing vacuously until the harness could script the tool calls it needed.

v1.0.5

Choose a tag to compare

@IvanWu2015 IvanWu2015 released this 06 Oct 13:06

for deepseek harness pc

v1.0.2

Choose a tag to compare

@IvanWu2015 IvanWu2015 released this 04 Oct 06:50
feat(connect): 1.0.2 — pane primary nav, 通用设置, one-click button first

The pane's top strip becomes a real primary navigation: 通用设置 first,
机器人渠道 second. The pane still *opens* on 机器人渠道 — deliberately, so the
one-click create button stays zero clicks away — and panel-state.mjs says so
at the definition, because the two facts read like a contradiction.

通用设置 exposes the ten values that previously required remembering a chat
command: language, notify level, progress timeout, work dir, workspaces, the
two whitelists, agent preset, auto mirror, stream heartbeat. All editable
through the same SettingsForms seam the channel cards use.

The load-bearing fix: SettingsForms.replace() resets every field it is not
handed, so a key missing from a save payload is erased, not untouched. The
one-click enable write projects the live config and writes the whole section
back (enableFeishuConfig is a `{ ...section, ... }` spread), so before this
release pressing it — or saving anything from the channels view — silently
reset all ten general keys. sectionOf now projects them, and mergeSections
starts from { ...base, ...override } instead of rebuilding key by key.

Also fixes a release blocker: scripts/bump-version.ps1 rewrote package.json
through ConvertFrom-Json | ConvertTo-Json and Set-Content -Encoding UTF8,
which on Windows PowerShell 5.1 writes a BOM. DSH's strict JSON parser then
printed `skipping profile bundle "dsh-connect"` and dropped the plugin
entirely — published fine, installed fine, did nothing. Now text surgery with
[System.IO.File]::WriteAllText + UTF8Encoding($false), and
test/manifest.test.mjs pins both the file's bytes and the script's idioms.

Removes the dead channelDefaults.notifyLevel (no adapter ever read it). A
profile carrying the key loses it on its next save through the declared
section — no behavioural change, but a visible one, so it is recorded in the
CHANGELOG rather than only here.

No config hot reload, and this release does not add one: ConnectService.config
resolves once at construction and the runner copies the general values into
instance fields. Rather than ship controls that appear to do nothing, every
general field says 「重启后生效」.

The model row is read-only by design. saveSelection() exists and is what
/model and /reasoning call, but that selection is DSH-wide and shared with
every other session and plugin, so the pane does not move it; the comment at
the point where someone would be tempted to wire it says why.

Docs: both README pairs, docs/config-reference.md, the PUBLISHING pair, and
the all-in-one guide; screenshots regenerated against a scratch profile
(14 PNGs, 7 names x zh/en). README.i18n.yaml re-recorded on both pairs.

1.0.1 rides along: it was written, version-bumped and never released — there
is no v1.0.1 tag and npm goes 1.0.0 -> 1.0.2. Its changelog entry now says so
rather than pretending it shipped.

v1.0.0

Choose a tag to compare

@IvanWu2015 IvanWu2015 released this 04 Oct 02:32
feat(connect): 1.0.0 — one-click Feishu bot creation, single-column s…

v0.9.2

Choose a tag to compare

@IvanWu2015 IvanWu2015 released this 02 Oct 07:18
fix(connect): anchor the one-shot import marker on the profile entry

The marker recorded "this profile entry has already consumed the home's
legacy document", but it was named after `${statePath}.legacy-imported` —
and `statePath` is the user's to move (`stateDir`, the
`DSH_CONNECT_STATE_DIR` override, `settingsStatePath`) and to delete.
Relocating the state directory, or removing one JSON file, made the next
boot believe no import had ever run: it re-read `settings.yaml.imported`
(where the host's own migration leaves the `dsh-connect:` section intact)
and layered the legacy values back over every pane edit made since. The
one outcome the marker exists to prevent, reachable by tidying up.

It now hangs off `profileContext.patchPath` instead, via a new
`legacyMarkerPath()` with an explicit precedence (markerPath → entryPath →
statePath → undefined). Per-profile isolation falls out of the same
choice: `$DSH_HOME` is shared by every profile, so a marker there would let
a second profile skip an import it never ran. The state-file suffix
survives only as a fallback for a host too old to hand over a profile
context.

`legacy-import.test.mjs` gains the anchor precedence, the regression test
that the marker stays put when the state file moves, per-profile isolation
over a shared home, and the fallback. 404 → 408 tests, all passing; the
client bundle is byte-identical (this is host-side only).

0.9.0

Choose a tag to compare

@IvanWu2015 IvanWu2015 released this 21 Sep 10:03
docs(connect): ship pane screenshots and bring all docs level with 0.9.0

Adds `docs` to the package's `files` whitelist and embeds six captures of the
settings pane (overview, an open 高级 fold, the shared-defaults card — each in
zh and en) in both package READMEs, so the layout described in the changelog can
be seen rather than only read. They come from a throwaway profile holding
placeholder credentials only; that is also the only kind of capture that can
exist, since the host masks values before they reach the browser.

Four factual corrections across the four READMEs, plus the same sweep over
docs/**:

- notifyLevel was documented as defaulting to `important` in all four. It has
  defaulted to `result` since fe4d051 (v0.7.1), a change the changelog never
  recorded, so the claim was copied forward from the 0.5.1 entry ever since.
- agentPreset was still described as a preset you set and get, with no mention
  of the degradation added in this release.
- The streaming section still credited the `assistant/chunk` session event that
  0.1.5-rc.2 deleted; the two feeds it was replaced by are now described.
- The one-click onboarding note still sent credentials to feishu-credentials.json.

Links that escape the published tarball are now absolute blob/main URLs; links
inside it stay relative. All 134 local references across READMEs and docs/
resolve. Both README.i18n.yaml manifests re-recorded.

v0.8.0

Choose a tag to compare

@IvanWu2015 IvanWu2015 released this 03 Sep 06:32
dsh-connect 0.8.0 — all-in-one channels + web settings