Commit Graph
352 Commits
Author SHA1 Message Date
ybc❤️zyrand猷诺 91af2bc3b8 fix(install): add .hermes/skills to AGENT_DIRS (#188) (#221)
* fix(install): add .hermes/skills to AGENT_DIRS (#188)

dws 安装后未自动复制 skill 到 .hermes/skills/。在 4 份硬编码的
AGENT_DIRS 清单(build/npm/install.js、scripts/install.sh、
scripts/install.ps1、scripts/install-skills.sh)末尾追加
.hermes/skills,与现有'父目录守卫'逻辑天然兼容:

- 已安装 Hermes 的用户:自动获得 ~/.hermes/skills/dws
- 未安装 Hermes 的用户:父目录不存在则跳过,零副作用

Closes #188

* fix(install): cover remaining 4 AGENT_DIRS sources for .hermes/skills (review feedback)

Address review feedback from #221: the AGENT_DIRS list actually has 8 sources
in the repo, not 4. Without this commit, dws upgrade users and Homebrew users
would still NOT get .hermes/skills/dws populated even after the previous
4-script fix landed.

Fixes (functional):
- internal/upgrade/paths.go (knownSkillDirs):
    Append .hermes/skills so 'dws upgrade' refreshes ~/.hermes/skills/dws/
    for users who have Hermes installed.
- build/homebrew.rb.tmpl (post_install targets):
    Append .hermes/skills/dws so brew users also get the skill copied to
    ~/.hermes/skills/dws on post_install.

Fixes (test / smoke coverage):
- test/scripts/package_script_test.go (expectedPackagedSkillTargets):
    Append .hermes/skills/dws so future regressions (e.g. someone removes
    .hermes from install.js) are caught by CI.
- scripts/release/verify-package-managers.sh (HOME_AGENT_PARENTS / HOME_SKILL_TARGETS):
    Append .hermes to both lists so the release-time npm/brew smoke test
    actually asserts ~/.hermes/skills/dws/SKILL.md exists.

Documentation:
- internal/upgrade/paths.go: expanded the 'Kept in sync with' comment from
    a single file reference (build/npm/install.js) to all 7 in-sync sources,
    so future maintainers don't have to rediscover this list.

Verification:
- go vet ./internal/upgrade/... ./test/scripts/...   PASS
- go build ./...                                     PASS
- sh -n scripts/install.sh                           PASS
- sh -n scripts/install-skills.sh                    PASS
- sh -n scripts/release/verify-package-managers.sh   PASS
- node --check build/npm/install.js                  PASS

---------

Co-authored-by: 猷诺 <bicheng.ybc@alibaba-inc.com>
2026-05-07 11:07:27 +08:00
ybc❤️zyrand猷诺 e2e8b3bf52 fix(attendance): summary 命令新增 --stats-type flag,修复 C0002 报错 (#228)
* fix(attendance): add --stats-type flag to summary to fix C0002 error

The MCP tool get_attendance_summary requires statsType in QueryUserAttendVO
at the server-side business layer (DingTalk schema marks it required:[] but
service rejects with C0002 'statistics type error' when omitted).

Changes:
- internal/helpers/attendance.go: add --stats-type flag (week/month);
  conditionally write statsType into QueryUserAttendVO when provided;
  update Long description and flag help to mark --stats-type as required
- test/cli_compat/attendance_test.go: add execSummaryDryRun helper and
  3 new tests verifying statsType is correctly threaded through VO
- skills/references/products/attendance.md: document --stats-type flag
  with warning about server-side mandatory enforcement

Verification:
- go build ./...                                                  PASS
- go vet ./internal/helpers/... ./test/cli_compat/...              PASS
- go test -run TestAttendanceSummary_should_pass_stats_type_*      PASS (3/3)
- go test -run TestAttendanceSummary_should_not_pass_stats_type_*  PASS
- Real CLI: dws attendance summary --stats-type month              success:true
  (C0002 fully gone; full attendance data returned)

Closes #227

* fix(attendance): enforce --stats-type as required at CLI layer (review feedback)

Address review feedback from #228:

1. Fail-fast validation: --stats-type is now required at the CLI layer
   instead of being conditionally written into VO. Previously the doc
   said 'required' but RunE silently let omission through, causing the
   same C0002 the fix was supposed to prevent.

2. Enum validation: only 'week' or 'month' are accepted; any other value
   is rejected at the CLI layer instead of being forwarded to the server.

3. Cosmetic: split the single-line Long description into multiple lines
   for readability.

Test changes:
- Replaced TestAttendanceSummary_should_not_pass_stats_type_when_omitted
  with two semantically-correct tests:
    * TestAttendanceSummary_should_error_when_stats_type_missing
    * TestAttendanceSummary_should_error_when_stats_type_invalid
- Updated other TestAttendanceSummary_* tests to pass --stats-type=month
  to match the new fail-fast contract.
- Renamed should_pass_only_user_flag to should_pass_user_id_through_vo
  (the old name no longer makes sense now that --stats-type is mandatory).

Skill doc:
- skills/references/products/attendance.md: clarified that the CLI now
  rejects missing/invalid --stats-type at the client side (won't even
  reach the server).

Verification:
- go build ./...                                    PASS
- go vet ./internal/helpers/... ./test/cli_compat/  PASS
- go test -run 'TestAttendanceSummary_should_(pass_stats_type|error_when_stats_type)' -v
                                                    PASS (4/4)
- dws attendance summary --help                     new layout shown

---------

Co-authored-by: 猷诺 <bicheng.ybc@alibaba-inc.com>
2026-05-07 10:50:24 +08:00
ybc❤️zyrand猷诺 a652b90fd4 fix: resolve cross-product tool routing collision (issue #219) (#220)
When two MCP servers register tools with the same name (e.g. both drive
and doc have 'create_folder'), the tool-level endpoint map uses
last-writer-wins, causing invocations to route to the wrong server.

Fix: swap Priority 1 (product-level) and Priority 2 (tool-level) in
directRuntimeEndpoint so that when the caller already knows the
productID, the product endpoint is authoritative. Tool-level lookup
remains as a fallback for cases where productID is empty.

This fixes 'dws drive mkdir' and 'dws drive download' being silently
routed to the doc MCP server instead of the drive MCP server.

Closes #219

Co-authored-by: 猷诺 <bicheng.ybc@alibaba-inc.com>
2026-05-05 16:21:51 +08:00
修雨 7a868ddf39 docs(help): document --contents.key=field_name and mark required flags (#106 #107) (#217)
- chat message send-by-bot --robot-code/--title/--text: append (必填) marker
  (RunE already enforces these as required; help text was missing the tag,
  causing downstream MCP wrappers to generate schemas with required fields
  missing).
- report create --contents: description appended "key must exactly equal the
  template field_name (look it up via report template detail --name <template>)";
  Long now warns about the API's SYSTEM_ERROR on key mismatch; Examples
  rewritten as a two-step pipeline (template detail → create).

Closes #106
Closes #107
2026-05-04 17:57:23 +08:00
ybc❤️zyrand猷诺 efb61cae02 fix: restore unconditional MCP re-fetch in login to prevent stale clientId errors (#213)
PR #184 merge conflict resolution inadvertently introduced a conditional
branch that preserved stale clientID from previous login sessions instead
of always re-fetching from MCP server. This caused exchangeCode() to use
direct DingTalk API mode (which requires clientSecret) instead of MCP
proxy mode, resulting in 'clientId或者clientSecret错误' errors.

This commit restores the original PR #157 logic: both DeviceFlowProvider
and OAuthProvider unconditionally call resetCredentialState() followed by
FetchClientIDFromMCP() + SetClientIDFromMCP(), ensuring exchangeCode()
always uses the MCP proxy path regardless of prior login state.

Root cause: the conditional branch treated any non-empty clientID (from
runtimeClientID or app.json) as a user-provided --client-id flag value,
skipping MCP re-fetch and leaving clientIDFromMCP=false after reset.

Affected scenarios:
- OAuth login → device flow login
- Any login → --force login
- New terminal with existing app.json → device flow login

Fixes regression introduced in PR #184 (commit 46192d6).
Restores fix from PR #157 (commit ad33a46).

Co-authored-by: 猷诺 <bicheng.ybc@alibaba-inc.com>
2026-05-04 09:26:25 +08:00
ybc❤️zyrand猷诺 fb88c6ace9 docs(todo): 增强待办命令帮助文本,补充字段语义说明 (#205)
基于实际 API 返回验证,所有描述均经过 dws 命令实测确认。

- todo task get: 新增 Long 说明
  · creatorId/executorIds/participantIds/modifierId 是待办内部短数字 ID
    (如 6380165826),非通讯录 userId (如 035551044606950179),已实测确认
  · 提示 creatorInfo/executorInfos/participantInfos 包含 name 属性可获取姓名
  · bizTag/source 返回 teambition 是引擎实现标识,已实测确认
  · tenantId 非 corpId,补充 tenantType 维度说明
- todo task list: 新增 Long 说明
  · 覆盖范围: 仅返回执行者维度待办,不含仅参与/仅创建的待办
  · 仅限个人待办,不含 OA 审批流/Teambition 项目任务
  · 自动分页说明
- todo task create: --executors flag 描述增强
  · 明确此处 userId 是通讯录 userId 并提示查询方式

Fixes #105

Co-authored-by: 猷诺 <bicheng.ybc@alibaba-inc.com>
2026-05-04 09:26:12 +08:00
xianfeng wangandgithub-actions[bot] 7692048cf4 fix(discovery): surface edition supplement/fallback servers in runtime catalog loader (#197)
* chore: update coverage badge [skip ci]

* chore: update coverage badge [skip ci]

* chore: update coverage badge [skip ci]

* fix conference discovery

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-30 09:44:25 +08:00
ybc❤️zyrand猷诺 426810a776 fix(todo): correct tool name from query_todo_detail to get_todo_detail (#202)
The todo task get command was calling query_todo_detail which returns
empty results. Changed to get_todo_detail to match the tool name
defined in discovery.json, restoring correct behavior.

Co-authored-by: 猷诺 <bicheng.ybc@alibaba-inc.com>
2026-04-29 22:59:37 +08:00
xuanandshangguanxuan.sgx da2522c8ca feat(pat)!: A-core loop for host-owned PAT (#142)
* feat(pat): A-core minimum viable loop for host-owned PAT

Enables third-party agents to integrate PAT via the smallest possible
contract surface. The flow a host needs to ship end-to-end:

  1. Export DINGTALK_DWS_AGENTCODE in the spawned shell.
  2. Run any `dws ...` business command.
  3. On PAT hit, CLI exits with code 4 and writes a single-line stderr
     JSON following docs/pat/contract.md §2.
  4. Host parses the JSON and invokes `dws pat chmod <scope>...
     --agentCode ... --grant-type ...` to grant.
  5. Host replays the original command.

This PR intentionally ships ONLY the chmod path plus the host-owned
switch; the async flow (apply / status / scopes + authRequestId
registry + PAT_SCOPE_AUTH_REQUIRED active-request branch) lands in a
follow-up stacked PR (A-ext).

Scope of A-core:
- Host-owned PAT trigger: auth.HostOwnsPATFlow() keyed exclusively on
  DINGTALK_DWS_AGENTCODE (DINGTALK_AGENT / DWS_CHANNEL / claw-type do
  NOT participate).
- claw-type: hard-wired to "openClaw" in pkg/edition/default.go
  MergeHeaders hook, matching historical main behavior and decoupled
  from DINGTALK_AGENT.
- dws pat chmod: factory-built (newChmodCommand) so the PAT subcommand
  tree has no shared package-level caller.
- stderr JSON classifier: internal/errors/pat.go covers PAT_NO_PERMISSION
  / PAT_LOW_RISK / PAT_MEDIUM_RISK / PAT_HIGH_RISK / PAT_SCOPE_*
  selectors and fills data.hostControl for host consumption.
- Env contract: DINGTALK_SESSION_ID / DINGTALK_TRACE_ID /
  DINGTALK_MESSAGE_ID carry HTTP trace headers (Chain B);
  DWS_SESSION_ID (+ REWIND_SESSION_ID alias) is the lone fallback for
  `dws pat chmod --session-id` (Chain A). The two chains are
  independent; aliases do not cross-pollinate.
- Docs: docs/pat/{README,contract,host-integration}.md + refreshed
  docs/architecture.md "PAT Architecture" chapter + docs/reference.md
  PAT section. error-catalog.md folded into contract.md §6.
- Tests: host-owned signal, stderr contract, classifier, chmod factory,
  retry / poll loop for the CLI-owned chmod path.

Compatibility: no breaking changes on main. All non-PAT commands are
untouched.

Follow-up PRs (in order):
- codex/pat-ext  : dws pat apply / status / scopes + AsyncRegistry.
- codex/pat-oss-refactor (aka PR B) : oauth_helpers / secure_store
  refactors split out per code review.

Made-with: Cursor

* fix(pat): unify host-owned stderr contract

* revert: drop PAT docs and changelog from pat core PR

* fix(pat): pin behavior auth endpoint

* fix(pat): recognize legacy tool miss

* fix(pat): fallback on gateway diagnostics

* fix(pat): treat authorization uri as opaque

* fix(pat): accept result envelope in device-flow polling

* feat(pat): add browser policy and poll compatibility

* fix(pat): keep CLI and host PAT contracts separate

* test(pat): isolate opaque uri retry env

* fix(pat): harden PAT result routing

* fix(pat): harden host-owned flow and runtime fallback

* fix(pat): preserve chmod failure on empty grant result

* refactor(pat): drop dead doc anchors, dedupe stderr injection helpers

Address two reviewer findings on top of the PAT-core series.

Fix:
- chmod: resolveSessionIDFromEnv silently selects DWS_SESSION_ID; drop
  the slog.Warn that emitted both raw session ids into stderr /
  ~/.dws/logs.
- Remove every docs/pat/contract.md / error-catalog.md /
  host-integration.md anchor from comments, help, and tests; those
  files were never added by this branch. Comments are now self-contained
  or point to docs/reference.md where applicable.

Refactor:
- internal/errors/pat.go: extract lookupCodeIn so getPATErrorCode and
  getDWSGatewayErrorCode share one traversal; have ClassifyPatAuthCheck
  delegate to getPATErrorCode instead of repeating the enum walk.
- internal/errors/pat.go + internal/app/pat_auth_retry.go: extract
  ApplyHostMutations as the single injection point for data.hostControl
  + data.openBrowser; cleanPATJSON and enrichPATErrorForHostControl now
  share it so the two stderr-JSON write paths cannot drift.
- internal/app/pat_auth_retry.go: drop buildPATScopeHostJSON and
  buildHostControlState dead 1-line wrappers.
- internal/pat/chmod.go: drop unused patApply/Status/Scopes constants;
  derive legacyToolArgs from toolArgs by clone + scopes->scope rename so
  the two payloads stay in lock-step on every other field.

Net 15 files, +176 / -193. Verification:

  env -u DINGTALK_DWS_AGENTCODE go test \
    ./internal/errors ./internal/auth ./internal/pat ./test/unit
  env -u DINGTALK_DWS_AGENTCODE go test ./internal/app \
    -run 'Test(IsPat|ExtractPat|PrintPat|PollPat|HandlePat|RetryWithPat|EnrichPAT|BuildPAT|DirectRuntime|ResolveIdentityHeaders)'
  env -u DINGTALK_DWS_AGENTCODE go test -race \
    ./internal/pat ./internal/errors ./test/unit
  env -u DINGTALK_DWS_AGENTCODE go test -race ./internal/app \
    -run 'Test(HandlePat|RetryWithPat|EnrichPAT|BuildPAT|ResolveIdentityHeaders|DirectRuntime)'
  go vet ./... && go build ./...

All green.

Made-with: Cursor

---------

Co-authored-by: shangguanxuan.sgx <shangguanxuan.sgx@alibaba-inc.com>
2026-04-28 21:17:23 +08:00
修雨 d11aa0f030 fix(chat,cmdutil): restore group @-mentions and explicit members list subcommand (#180)
* fix(chat,cmdutil): restore group @-mentions and members list subcommand

Two CLI regressions surfaced after PR #170 promoted hardcoded helpers over
the discovery envelope:

1. issue #177 — `dws chat message send --group ... --at-users ...` failed
   with `unknown flag: --at-users` on v1.0.16. The hardcoded helper that
   replaced the v1.0.15 envelope leaf only declared --group / --user /
   --open-dingtalk-id / --text / --title and silently dropped the envelope's
   --at-users / --at-all / --at-mobiles flags, so any group @-mention call
   bounced at cobra's flag parser before ever reaching the MCP tool.

   Add the three flags back to `newChatMessageSendCommand` and forward
   atUserIds / isAtAll / atMobiles to `send_message_as_user` in the
   `--group` branch; reject the flags loudly outside `--group` to avoid
   silently swallowing user intent in single-chat mode (single-chat tools
   have no @-mention semantics).

2. issue #164 — `dws chat group members --id <openConversationId>` returned
   `unknown flag: --id`. The helper had `members` as a "group with bare
   RunE + --id/--cursor flags" while the envelope publishes `members` as a
   leaf for `get_group_members`; the merge layer's shape-mismatch branch
   treats envelope as authority and silently drops the entire helper
   subtree (list / add / remove / add-bot all become unreachable).

   Restructure helper-side `members` into a pure group container and
   promote list to its own explicit subcommand:

     dws chat group members list --id <openconversation_id>

   And extend `pkg/cmdutil/MergeHardcodedLeaves` so the existing
   OverridePriority annotation also applies to the leaf↔group shape
   mismatch — when the helper group carries strictly higher priority it
   replaces the dynamic leaf, mirroring the leaf↔leaf override semantics
   that PR #170 already established. Without this the helper's `list`
   subcommand stays unreachable and the user-visible bug persists.

Tests:

- `internal/helpers/chat_test.go`: forwards-at-mentions (group + each
  flag variant), rejects-at-mentions-outside-group (single-chat
  validation), members-list-subcommand (structure + execution).
- `internal/app/legacy_test.go`: pickCommands integration test that
  reproduces the #164 shape mismatch end-to-end and asserts the helper
  subtree survives.
- `pkg/cmdutil/leaf_merge_test.go`: positive + boundary (equal priority)
  cases for the new leaf↔group promotion path.

Verified end-to-end against existing chat helper tests, app pickCommands
tests, cmdutil merge tests, cobracmd priority tests, compat dynamic
command tests — all green.

Skill docs (`skills/references/products/chat.md`) updated to teach the
new `members list` path with a migration note, and to surface the
restored `--at-mobiles` flag alongside `--at-all` / `--at-users`.

Closes #177
Closes #164

* fix(chat): resolve merge conflict in command Long text

Combine --title-required wording from #174 with at-mention
documentation introduced in this PR. CI build was failing on
unresolved <<<<<<< / >>>>>>> markers left in chat.go.
2026-04-27 22:56:46 +08:00
fantiu 41396426d0 Merge pull request #184 from fantiu/feat-rawapi
feat(api): add raw DingTalk OpenAPI command with dual-version token support
2026-04-27 21:14:15 +08:00
fantiu 6946929e0b feat(api): add raw DingTalk OpenAPI command with dual-version token support 2026-04-27 20:54:57 +08:00
xuanandshangguanxuan.sgx 6756ead38a fix: preserve PAT authorization uri and accept result poll envelope (#182)
* fix(pat): treat authorization uri as opaque

* fix(pat): accept result envelope in device-flow polling

* docs(pat): clarify empty-uri guard intent

* fix(pat): guard poll debug output and lock envelope priority

---------

Co-authored-by: shangguanxuan.sgx <shangguanxuan.sgx@alibaba-inc.com>
2026-04-27 20:04:01 +08:00
fantiu 4c44a2b632 Merge branch 'main' into feat-rawapi 2026-04-27 20:03:40 +08:00
fantiu e06b1d68ff feat: raw API update README. 2026-04-27 17:08:32 +08:00
修雨 b101d5b12b docs(skills,chat): clarify --title is required for chat message send (#174)
The backend MCP tools `send_direct_message_as_user` and
`send_message_as_user` both require `title` as a required field.
However, the skill documentation and --help Long text described
--title as "optional", causing AI models to omit it and trigger
a vague `business error: success=false`.

Update descriptions in:
- skills/references/products/chat.md
- internal/helpers/chat.go (command Long text)

Closes #173
2026-04-27 16:49:05 +08:00
xianfeng wangandgithub-actions[bot] acc5dc8a3c feat(plugins): register stdio plugin commands from CLI overlay before discovery (#179)
* chore: update coverage badge [skip ci]

* chore: update coverage badge [skip ci]

* plugin in help

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-27 16:20:35 +08:00
fantiu 592749c986 feat: The token retrieval interface is now unified to one. 2026-04-27 15:11:12 +08:00
fantiu 9b6174e03e feat(api): add raw DingTalk OpenAPI command with dual-version token support 2026-04-27 10:29:34 +08:00
Ari b0c9160ed5 fix(chat,cmdutil): route chat message send by destination flag (#170)
* fix(app): graft hardcoded helpers into dynamic tree instead of dropping

pickCommands previously dropped an entire helper subtree whenever a
top-level product name collided with the dynamic overlay. The intent was
to keep the discovery envelope as runtime authority for leaves it
declared, but the side effect was that every helper-only sibling
disappeared too — commands like `chat message send-by-bot`,
`chat message recall-by-bot`, `chat message send-by-webhook`, and
`chat group members add-bot` had silently vanished from the open
edition since the pickCommands introduction, making PR #161's
send-by-bot routing fix unreachable for open-source users.

Switch to cmdutil.MergeHardcodedLeaves for same-named products: the
dynamic side still wins every leaf conflict, and helper-only subtrees
are grafted into the dynamic tree. Envelopes remain authoritative for
leaves they declare; helpers once again fill the gaps they don't.

Verified on a freshly built open-edition binary:
  dws chat message send-by-bot --robot-code ... --users ... --text ...
returns a real processQueryKey and routes through canonical_product=bot
via helper_override, matching the v1.0.15 baseline.

* fix(chat,cmdutil): route chat message send by destination flag

`dws chat message send --user <userId>` failed with `不合法的参数` because
the envelope-generated dynamic command maps every destination to the
group-only tool `send_message_as_user`. Only `--group` was viable on the
open edition; single-chat (`--user`, `--open-dingtalk-id`) has never
worked end-to-end.

Fix is in two parts:

1. Add a hardcoded `chat message send` helper that dispatches by
   destination flag — `--group` → `send_message_as_user`, `--user` /
   `--open-dingtalk-id` → `send_direct_message_as_user`. This mirrors the
   closed-source wukong overlay's chatMessageSendCmd so the open edition
   lines up with the rest of the product.

2. Let hardcoded leaves opt into overriding the dynamic envelope in
   `cmdutil.MergeHardcodedLeaves` when they carry a strictly higher
   OverridePriority. The default remains "envelope is authority"; the
   opt-in exists for the narrow case where the envelope exposes a single
   dispatch path but the hardcoded leaf needs richer flag-based routing
   (as here). The new send helper uses preferLegacyLeaf (priority 100)
   so it wins against the dynamic leaf.

OverridePriority/SetOverridePriority move to pkg/cmdutil as the new
source of truth; internal/cobracmd/priority.go now delegates so the
annotation key stays single-sourced across the merge layer and helpers.

Verified end-to-end against a freshly built open-edition binary:
  send --user 034766 --text …  → send_direct_message_as_user → success
  send --group <cid> --text …  → send_message_as_user          → success
2026-04-24 20:41:30 +08:00
Ari 0b544a86b7 fix(app): graft hardcoded helpers into dynamic tree instead of dropping (#169)
pickCommands previously dropped an entire helper subtree whenever a
top-level product name collided with the dynamic overlay. The intent was
to keep the discovery envelope as runtime authority for leaves it
declared, but the side effect was that every helper-only sibling
disappeared too — commands like `chat message send-by-bot`,
`chat message recall-by-bot`, `chat message send-by-webhook`, and
`chat group members add-bot` had silently vanished from the open
edition since the pickCommands introduction, making PR #161's
send-by-bot routing fix unreachable for open-source users.

Switch to cmdutil.MergeHardcodedLeaves for same-named products: the
dynamic side still wins every leaf conflict, and helper-only subtrees
are grafted into the dynamic tree. Envelopes remain authoritative for
leaves they declare; helpers once again fill the gaps they don't.

Verified on a freshly built open-edition binary:
  dws chat message send-by-bot --robot-code ... --users ... --text ...
returns a real processQueryKey and routes through canonical_product=bot
via helper_override, matching the v1.0.15 baseline.
2026-04-24 20:32:05 +08:00
fantiu 46192d68ba feat(api):client logic fixed. 2026-04-24 17:04:09 +08:00
fantiu eeb1782f7a feat(api): add raw DingTalk OpenAPI command with dual-version token support 2026-04-24 16:40:13 +08:00
ybc❤️zyrand猷诺 ad33a46883 fix: device flow defensively resets credentials before login (#155) (#157)
Device flow now always clears stale credential state and re-fetches
clientID from MCP server, regardless of what previous login methods
(OAuth scan, PAT, etc.) left in app.json or runtime globals.

This replaces the previous Source-field approach with a simpler
defensive reset that is future-proof against new login methods.

Root cause: after OAuth login saved app.json without MCP source marker,
subsequent --device login reused the stale clientID without setting
clientIDFromMCP flag, causing exchangeCode() to use direct mode which
requires clientSecret.

Changes:
- Add resetCredentialState() to DeviceFlowProvider
- Login() always calls resetCredentialState() + FetchClientIDFromMCP()
- Remove AppConfig.Source field (no longer needed)
- Remove app_config_source_test.go (replaced by device_flow_reset_test.go)
- Add 5 tests covering OAuth→device, legacy→device, direct→device scenarios

Co-authored-by: 猷诺 <bicheng.ybc@alibaba-inc.com>
2026-04-24 14:11:16 +08:00
xianfeng wang bbca507792 feat(discovery): MCP discovery service abstraction with positional params, examples, defaults, and schema v3 extensions (#156)
* discovery

* discovery

* wk discovery

* wk discovery

* wk discovery

* wk discovery

* wk discovery

* wk discovery

* discovery example and default

* position param

* Fix test

* dev auto login

* fix test

* fix conflict
2026-04-24 14:08:17 +08:00
Ari 97390829dd fix(compat,helpers): json_parse YAML fallback + send-by-bot product routing (#161)
* fix(compat): accept YAML flow as a fallback for json_parse transform

Users who run `dws aitable table create --fields [{fieldName: 标题, ...}]`
hit a shell glob error (`zsh: bad pattern: [{fieldName:`) because the
unquoted brackets are interpreted as a zsh pattern. Even when the shell
is placated with outer quotes, the strict-JSON parser still rejected the
natural form that drops quotes around keys and string values.

transformJSONParse now tries strict JSON first (unchanged fast path) and,
on failure, falls back to yaml.Unmarshal. YAML's flow syntax is a
superset of JSON that accepts `{key: value}` without surrounding quotes,
so a single set of outer quotes is all the user needs:

  dws aitable table create --fields '[{fieldName: 标题, type: text}]'

Already-valid strict JSON is unaffected. The @file syntax (`--fields
@path.json`) continues to work through the same transform.

The terminal error message, if both parsers fail, now points users at
the two working forms (quoted YAML flow or @file).

* fix(helpers): route chat send-by-bot to bot product

`chat message send-by-bot` previously stamped its invocation with
CanonicalProduct="chat", routing it through the chat product's auth /
server dispatch. The command is semantically a bot operation — switch
it to "bot" so auth client selection and downstream routing match the
actual MCP server. Extends chat_test.go with single-chat and group-chat
routing assertions.
2026-04-24 01:36:32 +08:00
Ari 7828089081 feat: subcommand merging, schema pretty, IR overlays, v1.0.15 notes (#160)
* feat(compat): merge same-name subcommands under shared parent

When multiple server entries attach to the same parent and their cli.command
collides with a subcommand already in the parent tree, the incoming subcommand's
children are merged recursively into the existing one instead of producing a
duplicate sibling. Leaf-name collisions resolve first-wins.

Fixes the duplicate `group` / `message` rows in `dws chat --help` that surfaced
when bot capabilities were distributed across chat.group.members and
chat.message subtrees.

* feat(ir): carry FlagOverlay and ToolAnnotations into canonical catalog

FlagOverlay mirrors the per-parameter CLI overlay (alias, transform,
transformArgs, env default, default value, hidden) sourced from
market.CLIToolOverride.Flags.

ToolAnnotations mirrors MCP 2025+ tool annotations with nullable hints
(destructive / read-only / idempotent / open-world) so absence means
"unknown" rather than "false".

BuildCatalog now propagates both through the canonical layer, with tests
covering group metadata and flag-overlay passthrough.

* feat(output): add -f pretty for schema-aware colored output

dws schema now supports -f pretty alongside json / raw. Pretty mode
partitions output by product / tool / parameter / enum and applies ANSI
color so humans can browse the catalog without piping through jq.
json / raw behaviour is unchanged.

docs/reference.md gains a Schema Introspection section documenting
dws schema and the new pretty format.

* feat(skills): refresh 13-product references; add devdoc / drive / oa

SKILL.md cli_version bumped to >=1.0.15; product index reordered to match
the v1.0.15 command surface.

New references:
- devdoc.md: Open Platform documentation search
- drive.md: DingTalk drive (promoted out of Coming soon)
- oa.md: OA approval end-to-end flow

Major rewrites for aitable (dashboard / chart share, import/export,
attachment flow), chat (bot capabilities merged into chat.message and
chat.group.members), calendar (event suggest / attachments), doc (comment
subtree, file create, upload/download flow), and minutes (list
mine/shared/all unified, record subcommands split).

Minor alignment touch-ups for contact / report / todo flag names.

* docs(release): sync v1.0.15 notes — 159 commands across 13 products

README Key Services fully refreshed: chat 23, calendar 14, aitable 41,
doc 21, minutes 19; drive promoted out of Coming soon; workbench and
standalone bot rows removed; Quick Start expanded to 7 examples spanning
doc / minutes / drive; Coming soon trimmed to mail / conference / aiapp /
live / wiki.

Adds docs/command-index.md, an auto-generated English listing of all 159
runtime commands with description and when-to-use guidance aimed at AI
agents. Replaces the ad-hoc command-index.pre.* / command-index.full.*
snapshots used during development.

CHANGELOG 1.0.15 records the compat subcommand-merging feature, the new
command index, and a flag-naming cleanup across chat / calendar / drive /
minutes / contact / devdoc.
2026-04-24 00:01:22 +08:00
xuan 89c5038446 fix(auth): restore device_code polling when flowId is absent (#145)
* fix(auth): fallback to device_code when flowId is missing

* chore(auth): remove dead helper and harden fallback tests
2026-04-22 15:13:40 +08:00
修雨 4f915e4e2c refactor(plugin): drop managed/ compat layer left by PR #126 (#135)
PR #126 removed the privileged managed-plugin mechanism but kept
LoadManaged, IsManaged, and ~/.dws/plugins/managed/ fallbacks so that
plugins installed by pre-#126 builds would keep loading. The window
for that migration was ~4 days (2026-04-15 → 2026-04-19), and the
original install path mostly failed anyway (issue #124 / GitHub Pages
HTML response), so there is no real user base to preserve.

Scope of deletion:
- plugin.Loader.LoadManaged, scanDir's isManaged param, loadPlugin's
  isManaged param. LoadAll now combines user + dev only.
- Plugin.IsManaged field. Converter/root.go no longer branch on it;
  stdio and HTTP descriptors both report source="plugin".
- ~/.dws/plugins/managed/ branches in ListInstalled, SetEnabled,
  RemovePlugin.
- config.PluginManagedDir constant.
- TestLoaderLoadManaged, TestRemoveLegacyManagedPlugin, and the
  "legacy managed" table case in TestRemovePluginPurgesSettings.

Kept on purpose:
- Manifest.Type public schema field ("managed"|"user") — it appears
  in existing plugin.json files and is only validated, never
  behaviorally consumed.

Users with an orphaned ~/.dws/plugins/managed/ directory can
`rm -rf ~/.dws/plugins/managed/` — the CLI no longer reads it.
2026-04-22 14:56:11 +08:00
fantiuandgithub-actions[bot] e0544579d2 fix(auth): attribute CLI auth denial to channel before user scope (#144)
* chore: update coverage badge [skip ci]

* chore: update coverage badge [skip ci]

* fix(auth): attribute CLI auth denial to channel before user scope

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-22 12:05:30 +08:00
383aeefaf6 fix(cli): localize plugin/help command strings via i18n (closes #118) (#134)
Wire plugin module + help command + OAuth client-id/secret flags through
the existing i18n catalog so --help is consistent Chinese under zh locale
and English under en locale, instead of mixing the two.

- internal/app/plugin_cmd.go: wrap 16 Short strings with i18n.T
- internal/app/flags.go: wrap --client-id/--client-secret descriptions
- internal/app/root_help.go: override cobra default help command with a
  localized one so "dws help --help" and the utility-commands listing
  share the same catalog
- internal/i18n/locales/{en,zh}.json: add 20 new catalog entries

Verified under DWS_LANG=zh (all Chinese), DWS_LANG=en (original English
preserved), and LANG-based auto-routing. go test ./internal/app/... pass.

Co-authored-by: 修雨 <huyizhou.hyz@alibaba-inc.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-20 20:51:07 +08:00
ybcstudyand猷诺 a5bede3a19 fix: -f json模式下错误JSON从stdout改为输出到stderr (#133)
修复背景:
CI测试脚本通过检查stderr中是否包含错误关键词来判断命令是否失败。
但-f json模式下,printExecutionError将错误JSON输出到stdout,
导致stderr为空,测试断言失败。

改动:
- root.go:150: apperrors.PrintJSON(stdout, err) → apperrors.PrintJSON(stderr, err)
- root_execute_test.go: 3处断言方向同步调整(stdout↔stderr)

影响范围:
仅影响-f json模式下的错误输出流向,正常输出不受影响。
符合Unix惯例:错误信息输出到stderr,正常结果输出到stdout。

Co-authored-by: 猷诺 <bicheng.ybc@alibaba-inc.com>
2026-04-20 19:38:39 +08:00
xuan bbf66e23d6 fix(pat): avoid shared PAT command state in root registration (#129)
* chore: remove workspace and bundled artifacts

* chore: clean local-only repository artifacts

* fix(pat): avoid shared command state in registration
2026-04-20 14:54:57 +08:00
shangguanxuan633-labandshangguanxuan.sgx 725577103d feat: PAT scope error visualization and auto-retry with authorization polling (#113)
* feat: PAT scope error visualization and auto-retry with authorization polling

1. PAT error result visualization (non-JSON)
   - Human-readable error output matching lark-cli style
   - Shows error type, message, hint, and authorization command
   - JSON output also available via --format json

2. Auto-retry with polling after authorization wall
   - Detects missing_scope / insufficient_scope / permission errors
   - Polls every 5s for token update after user completes auth
   - 10 minute timeout before giving up
   - Automatically retries the original command after successful auth

Changes:
- internal/app/pat_auth_retry.go: Core PAT error handling and retry logic
- internal/app/pat_auth_retry_test.go: 11 unit tests
- internal/app/runner.go: Integration with executeInvocation error path
- internal/auth/device_flow.go: Add SetScope() method

* feat: migrate PAT (Permission Authorization) infrastructure to open-source core

Move PAT permission management code from dws-wukong overlay to the
open-source dingtalk-workspace-cli core. This enables PAT handling
in the public distribution while keeping dws-wukong fully compatible
through the edition.Hooks mechanism.

Changes:
- pkg/edition: add 6 new Hooks fields (AuthClientID, AuthClientFromMCP,
  SaveToken, LoadToken, DeleteToken, ClassifyToolResult) for overlay
  extensions to inject custom auth/token/error behaviour
- internal/errors/pat: new PATError type with ExitCode()=4 and
  RawStderr() for embedded-mode passthrough, plus ClassifyToolResultContent
  and ClassifyMCPResponseText classification functions
- internal/pat: new command group (pat chmod) with tool result handling
- internal/app/root: register PAT commands before RegisterExtraCommands hook
- internal/app/runner: integrate ClassifyToolResult hook in the
  callResult.IsError branch so overlays can intercept PAT/gateway errors
  before generic handling

The dws-wukong overlay remains unchanged — its existing pat/ package and
RegisterExtraCommands hook continue to work. Through the go.mod replace
directive and deduplicateCommands mechanism, both codebases coexist
without conflict.

* feat(pat): add PAT auth check with device flow polling and auto-retry

- Add ClassifyPatAuthCheck / AsPatAuthCheckError in pat.go for
  AGENT_CODE_NOT_EXISTS error detection
- Add handlePatAuthCheck in pat_auth_retry.go: inject clientId as
  x-robot-uid header, poll device flow endpoint, auto-retry on APPROVED
- Integrate PAT check into runner.go executeInvocation pipeline
  (edition hook + open-source fallback)
- Add patAuthRequiredCodes map for extensible auth-required code matching
- Remove test_parse.go (temporary test script)

* fix(pat): update poll path to /cli/oauth/device/poll and add x-user-access-token header

- Change DevicePollPath from /api/dingtalk-workspace-cli/oauth/device/poll
  to /cli/oauth/device/poll (aligning with backend endpoint convention)
- Add x-user-access-token header to poll requests (loaded from stored token)

* feat(auth): PAT device flow improvements and flowId empty guard

- pat_auth_retry: fix JSON field parsing (result -> data) using DevicePollResponse
- pat_auth_retry: fix elapsed time calculation based on start timestamp
- device_flow: add flowId empty guard - skip polling and show auth URL for manual handling
- device_flow: unify terminalBaseURL to GetMCPBaseURL for polling endpoint
- device_flow: remove unused PollFlowApproval dead code
- runner: integrate DWS_CLIENT_ID env and app.json persistence
- oauth_provider: add x-robot-uid header removal on logout
- client: add request/response logging for transport debugging

* feat(env): switch all service discovery URLs from pre-release to production

- endpoints: pre-login/pre-api/pre-mcp/pre-open-dev -> login/api/mcp/open-dev
- loader: DefaultMarketBaseURL -> mcp.dingtalk.com
- registry: defaultBaseURL -> mcp.dingtalk.com
- constants: DefaultTerminalBaseURL -> open-dev.dingtalk.com

* fix: remove residual merge conflict markers and duplicate Hooks fields in edition.go; unify developer-settings URL via config helper in device_flow.go

* chore: remove lark-cli reference from PrintPatAuthError comment

* test: add comprehensive unit tests for PAT auth modules

- Create internal/errors/pat_test.go (38 tests): covers ClassifyToolResultContent,
  ClassifyMCPResponseText, ClassifyPatAuthCheck, AsPatAuthCheckError, cleanPATJSON,
  stripClassFields, getDWSGatewayErrorCode, isNotLoggedInError, isBusinessError,
  IsPATError, IsPATNoPermissionCode, suggestForBusinessErrorText
- Supplement pat_auth_retry_test.go (+7 tests): IsPatRetrying context checks,
  pollPatDeviceFlow edge cases (server error fallback, SSO redirect skip),
  extractPatScopeError nil/identity extraction

* feat(pat): exchange authCode for fresh token after PAT APPROVED

Previously pollPatDeviceFlow discarded the authCode from the poll
response, so handlePatAuthCheck retried with the stale token after
APPROVED. This mirrors device_flow.go loginOnce which calls
exchangeCode → SaveTokenData.

Changes:
- pollPatDeviceFlow: return (status, authCode, error) instead of
  (status, error); extract Data.AuthCode on APPROVED
- handlePatAuthCheck APPROVED path: call ExchangeCodeForToken +
  SaveTokenData before ResetRuntimeTokenCache, with graceful
  fallback on exchange failure
- Update all 6 poll test cases for new 3-return signature; add
  authCode assertions for APPROVED/REJECTED/EXPIRED/CANCELLED/
  ServerError scenarios

* fix(pat): CR round-2 must-fix items

- scopeValueRegex: support multi-segment scopes (mail:a.b:send)
  Updated regex to ([a-zA-Z][a-zA-Z0-9_.]*(?::[a-zA-Z][a-zA-Z0-9_.]*)+)

- handlePatAuthCheck: add 3 integration tests (APPROVED/REJECTED/EmptyFlowID)
  Covers the 132-line main orchestrator with mock runner + httptest server

- Extract ParseDeviceFlowStatus + status constants to auth package
  Eliminates string literal duplication across device_flow.go and pat_auth_retry.go

* fix(pat): use direct OAuth path when clientSecret is provided

When PAT error response includes both clientId and clientSecret,
use SetClientID() (direct DingTalk API mode) instead of
SetClientIDFromMCP() (MCP proxy mode). The MCP proxy does not
hold the secret for the PAT-assigned app, causing HTTP 400
'invalidParameter.idOrSecret.notFound' on token exchange.

Now: clientSecret present → direct mode; absent → MCP proxy mode.

---------

Co-authored-by: shangguanxuan.sgx <shangguanxuan.sgx@alibaba-inc.com>
2026-04-19 19:12:34 +08:00
修雨 f762117d4e Merge pull request #126 from DingTalk-Real-AI/refactor/remove-managed-plugin-privilege
refactor(plugin): 移除 default/managed plugin 特权机制
2026-04-19 14:56:59 +08:00
修雨 59e51c348a perf(plugin): share cache.Store across discovery + relax cold timeouts
Address reviewer feedback on #125:

- Hoist a single *cache.Store above the discovery fan-out so all HTTP and
  stdio goroutines share one instance rather than each spawning its own in
  registerHTTPServer/registerStdioServer. Atomic tmp+rename per cache key
  keeps concurrent writers on distinct keys collision-free; global runtime
  registries (AppendDynamicServer, RegisterStdioClient) already guard
  themselves with a mutex. Added a comment pointing at those invariants.

- Relax cold-path timeouts to survive healthy cross-region endpoints and
  Python/Node interpreter warm-up: HTTP 500ms → 1s (plain), 700ms → 1.5s
  (auth), stdio 1s → 2s. A new DWS_PLUGIN_COLD_TIMEOUT env var overrides
  all three with a single duration, registered in configmeta so it shows
  up in `dws config --help`.

- Add internal/app/plugin_discovery_concurrency_test.go covering the
  thread-safety claims end-to-end: 16 parallel SaveTools with distinct
  keys, 32 parallel AppendDynamicServer, 32 parallel RegisterStdioClient,
  plus TestResolvePluginColdTimeouts exercising defaults, a valid
  override, an unparseable value, and a non-positive value. All pass with
  go test -race.

Verified: go build ./... clean; targeted -race tests PASS in 1.5s;
./internal/{app,cli,cache,plugin}/... all green.
2026-04-19 14:41:40 +08:00
修雨 a056a9abfb refactor(plugin): purge removed plugin settings instead of merely disabling
Review follow-up on #126: RemovePlugin used to call
setPluginEnabled(name, false), which left the plugin's key in
EnabledPlugins and never touched PluginConfigs. settings.json retained
dangling state for plugins no longer on disk.

Replace the disable with purgePluginFromSettings, which deletes both
the EnabledPlugins entry and any PluginConfigs entry for the removed
plugin, leaving unrelated plugins' state intact. A re-install defaults
to enabled=true via the existing install paths, matching user
expectations.

Covered by TestRemovePluginPurgesSettings across both user and legacy
managed layouts.
2026-04-19 14:32:22 +08:00
修雨 33ae780103 refactor(plugin): remove default/managed plugin privileged mechanism
Drop the hardcoded default-managed-plugin bootstrap that auto-fetched
DingTalk-Real-AI/* plugins on CLI startup. All third-party plugins are
now installed equally via `dws plugin install` — no privileged workspace.

Motivation (issue #124): first-run startup printed
  WARN failed to fetch remote info for default plugin
       DingTalk-Real-AI/conference: invalid character '<' looking for
       beginning of value
because the GitHub Pages registry returned HTML for the missing plugin.
Beyond the error, the whole "built-in plugin" concept contradicts the
CLI's lightweight design: plugin installation planning belongs to
agent-authored skills, not to the CLI binary.

Changes:
- Delete internal/plugin/updater.go (EnsureManaged, CheckAndUpdate,
  checkRemoteVersion, downloadAndInstall, promptUpdate, zip extraction
  — the entire managed-plugin update pipeline).
- Delete internal/plugin/updater_test.go.
- Remove DefaultManagedPlugins, OfficialPluginWorkspace, and
  PluginUpdateCheckInterval from pkg/config/constants.go.
- Remove the EnsureManaged/CheckAndUpdate bootstrap block from
  internal/app/root.go; only legacy LoadManaged() kept for back-compat
  so plugins already installed under ~/.dws/plugins/managed/ still load.
- internal/plugin/loader.go: InstallFromGit always installs under
  PluginUserDir; RemovePlugin allows removal of legacy managed-dir
  plugins instead of refusing.
- internal/app/plugin_cmd.go: drop `--type managed` flag and related
  gating from `plugin install`; disable/remove commands no longer
  differentiate managed vs user.
- Update plugin_test.go accordingly (replace TestRemoveManagedPluginBlocked
  with TestRemoveLegacyManagedPlugin, drop TestPromptUpdate).

Backward compatibility: `PluginManagedDir = "managed"` constant retained
so plugins already on disk from older CLI versions still load and are
removable. No migration required.

Verification:
- go build ./cmd                 → success
- go test ./internal/plugin/...  → PASS
- go test ./internal/app/...     → PASS (436s, matches main baseline)
- Smoke: `/tmp/dws-new plugin list` no longer prints the "Pulling
  built-in plugin" or "failed to fetch remote info" WARN.
- Pre-existing failures on main (unrelated): test/cli_compat,
  test/integration/extensions (requires auth login),
  test/scripts (requires DWS_PACKAGE_VERSION or git tag),
  test/unit TestOpenSourceTreeOmitsEmbeddedHostMarkers.

Closes #124
2026-04-19 11:18:56 +08:00
修雨 daf56514f7 perf(plugin): parallelize all plugin discovery and tighten cold timeouts
Follow-up to cache-first registration (c95ec04). The cold-cache wall
clock is now bounded by the slowest individual plugin, not the sum:

- Fan out HTTP and stdio discovery together in goroutines rather than
  running the stdio loop serially after HTTP.
- HTTP cold budget: 4s → 700ms (auth) / 500ms (plain). Honest dial
  timeouts on unreachable endpoints fail fast; healthy third-party
  endpoints respond well under the window. The outcome is still saved
  as a negative cache, so a miss this run costs the next run ~0ms.
- Stdio cold budget: 4s → 1s. A local subprocess handshake is milliseconds.

With three user plugins (one pointing at TEST-NET-1, permanently
unreachable), cold `dws --help` drops from ~3.3s to ~0.8s and warm stays
at ~80ms. The savings compound linearly with plugin count.
2026-04-18 14:22:53 +08:00
修雨 8bcbceb971 perf(plugin): serve plugin MCP tool list from disk cache on startup
Follow-up to the 4s startup cap (df01f36). Plugin command registration
now reads the tools snapshot directly from the cache store on the hot
path, and only falls through to an Initialize+ListTools RPC when no
snapshot exists.

- registerHTTPServer / registerStdioServer try cache.LoadTools first;
  on hit they build Cobra commands synchronously with zero network I/O.
- Cold cache falls back to the existing synchronous discovery (already
  bounded at 2-4s) and persists the outcome — including empty tool
  lists — as a negative cache so the next invocation is fast regardless
  of endpoint health.
- Cache entries are namespaced under "plugin:<name>:<server>" so they
  are distinct from Market cache entries in `dws cache status`, and
  refresh on-demand via `dws cache clean` / `dws cache refresh`; the
  existing 7d ToolsTTL otherwise expires entries naturally.

Warm-cache `dws --help` with three user plugins (one unreachable) now
returns in ~80ms versus 3.7s with synchronous discovery, a >40x win
when endpoints are offline.
2026-04-18 00:47:20 +08:00
修雨 df01f36442 fix(transport): cap plugin MCP startup at ~4s when endpoints are unreachable
Issue #119: an unreachable third-party MCP plugin (e.g. blocked/firewalled
endpoint) blocks `dws --help` for ~10s on every CLI invocation, because
plugin discovery happens eagerly during command-tree construction.

Root cause was a stack of timeouts that multiplied under transport failure:

* `transport.Client.Initialize` loops three supported protocol versions on
  every error — including dial timeouts and HTTP 5xx — even though those
  failure modes are protocol-version-independent. Three loops × the dial
  budget = 3× the worst-case startup cost.
* Default `DialContext.Timeout` was 10s, so a single dial against an
  unroutable address (e.g. TEST-NET-1) burned the full plugin context.
* `registerHTTPServer` granted plugins with `AuthHeaders` a 10s outer
  context (intended for slow third-party services), and `registerStdioServer`
  granted every stdio plugin 10s. Either single misbehaving plugin therefore
  stalled the entire CLI.
* Registry-side `defaultDiscoveryTimeout` (10s) and `perServerDiscoveryTimeout`
  (5s) had the same shape on the LoadCatalog path.

Changes:

* `transport.Client.Initialize`: short-circuit when the underlying
  `*CallError.Stage` is anything other than `CallStageJSONRPC`. Protocol
  version negotiation is the only justification for retrying with another
  version; transport/HTTP failures fail identically and should surrender.
* `transport.defaultTransport`: `DialContext.Timeout` 10s → 3s.
* `app.registerHTTPServer`: AuthHeaders timeout 10s → 4s.
* `app.registerStdioServer`: outer ctx timeout 10s → 4s.
* `cli.defaultDiscoveryTimeout`: 10s → 4s.
* `discovery.Service`: rename `perServerDiscoveryTimeout` 5s → 2s
  (`defaultPerServerDiscoveryTimeout`); add `Service.PerServerTimeout`
  override field for tests/callers needing a tighter or looser bound.

Tests:

* `TestInitializeShortCircuitsOnHTTPError` — asserts only ONE protocol
  version is attempted on HTTP 5xx.
* `TestInitializeShortCircuitsOnDialFailure` — asserts Initialize returns
  in <2s on a refused-connection address.
* Existing `TestInitializeNegotiatesProtocolVersion` continues to pass —
  JSON-RPC-stage errors still trigger version fallback.

End-to-end measurement with a blackhole plugin
(endpoint=192.0.2.1, AuthHeaders set):

  | Variant       | median `dws --help` |
  | ------------- | ------------------- |
  | main          | 10.2s               |
  | PR #121 alone | 9.1s                |
  | this branch   | 3.72s (-63%)        |

The `loader.go` and `discovery/service.go` timeout reductions overlap with
PR #121 by @utafrali; see PR description for attribution.

Refs: #119
Supersedes: #121
2026-04-17 17:45:35 +08:00
修雨 b0024aa669 Merge pull request #122 from fantiu/feat-claw
fix(auth): exit immediately on terminal auth denial and unify denial page styles
2026-04-17 16:05:20 +08:00
扶疏 94ad422a9f style: fix code formatting 2026-04-17 12:19:27 +08:00
fantiu 4f1ee37508 fix(auth): improve login UX for terminal auth denial cases 2026-04-17 10:20:35 +08:00
fantiu fec0347cd6 feat(auth):improve login UX for terminal auth denial cases 2026-04-17 10:15:04 +08:00
扶疏 93318f4a83 fix(plugin): stop stdio child processes on exit and before removal 2026-04-17 10:03:01 +08:00
扶疏 a14fd0250c feat(plugin): inject user identity (UserID, CorpID) into stdio plugin subprocesses 2026-04-17 10:02:26 +08:00
fantiu c99e228669 Merge remote-tracking branch 'upstream/main' into feat-claw
merge main commit
2026-04-16 19:59:19 +08:00
fantiu 95d495f290 feat: supports claw-like products. 2026-04-16 19:49:28 +08:00
wxianfeng 4851d19141 Merge remote-tracking branch 'upstream/main' 2026-04-16 14:53:07 +08:00