@aiwuk/team 0.0.0-stage → 5.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/CHANGELOG.md +156 -0
  2. package/LICENSE +21 -0
  3. package/README.md +149 -2
  4. package/README.zh-CN.md +150 -0
  5. package/access.js +93 -0
  6. package/activity-summary.js +36 -0
  7. package/allocation.js +34 -0
  8. package/board-dispatch.js +141 -0
  9. package/brand.js +41 -0
  10. package/client/00-core-zh.js +284 -0
  11. package/client/01-i18n-en.js +265 -0
  12. package/client/02-styles.js +336 -0
  13. package/client/02b-stage-styles.js +65 -0
  14. package/client/03-shared.js +82 -0
  15. package/client/03b-collapsible-list.js +34 -0
  16. package/client/03c-config-panel.js +605 -0
  17. package/client/04-tool-cards.js +110 -0
  18. package/client/04a-dispatch-confirm.js +162 -0
  19. package/client/04b-more-cards.js +78 -0
  20. package/client/04c-model-picker.js +68 -0
  21. package/client/04d-confirm-dock.js +40 -0
  22. package/client/05-policy.js +217 -0
  23. package/client/06-usage.js +195 -0
  24. package/client/06b-model-source.js +67 -0
  25. package/client/07-board-parts.js +312 -0
  26. package/client/07a-role-theme.js +25 -0
  27. package/client/08-artifacts-report.js +375 -0
  28. package/client/08a-task-control.js +126 -0
  29. package/client/08b-stage-sprites.js +97 -0
  30. package/client/08c-stage-colors.js +55 -0
  31. package/client/08c-stage-scene.js +238 -0
  32. package/client/08d-stage-adapter.js +65 -0
  33. package/client/08e-stage-activity.js +83 -0
  34. package/client/09-team-board.js +181 -0
  35. package/client/09b-role-view.js +95 -0
  36. package/client/09c-model-tabs.js +20 -0
  37. package/client/10-overview.js +364 -0
  38. package/client/10b-onboarding.js +121 -0
  39. package/client/11-register.js +249 -0
  40. package/client.js +5053 -0
  41. package/config-projection.js +40 -0
  42. package/config.js +411 -0
  43. package/cordis.patch.yml +110 -0
  44. package/defaults.js +222 -0
  45. package/dispatch-confirm.js +293 -0
  46. package/dispatch-routes-store.js +31 -0
  47. package/failover.js +85 -0
  48. package/file-limits.js +119 -0
  49. package/free-worker.mjs +125 -0
  50. package/http-guard.js +14 -0
  51. package/i18n/roles.en.json +23 -0
  52. package/i18n/roles.zh.json +23 -0
  53. package/i18n/server.en.json +659 -0
  54. package/i18n/server.zh.json +659 -0
  55. package/i18n.js +34 -0
  56. package/icon.svg +7 -0
  57. package/index.js +111 -0
  58. package/integrations-config.js +301 -0
  59. package/integrations-messages.js +62 -0
  60. package/integrations.js +17 -0
  61. package/lead-route.js +30 -0
  62. package/locale/en.json +6 -0
  63. package/locale/zh.json +6 -0
  64. package/long-context.js +30 -0
  65. package/managed-config-route.js +49 -0
  66. package/managed-ref.js +38 -0
  67. package/managed-resolve.js +47 -0
  68. package/member-task-control.js +242 -0
  69. package/memory-backends.js +89 -0
  70. package/memory-embed.js +70 -0
  71. package/memory-hub.js +150 -0
  72. package/memory-openviking.js +101 -0
  73. package/memory-redact.js +34 -0
  74. package/memory-store.js +219 -0
  75. package/memory-sync-queue.js +28 -0
  76. package/model-policy.js +85 -0
  77. package/model-prefs.js +104 -0
  78. package/observability-api.js +76 -0
  79. package/observability-projection.js +141 -0
  80. package/onboarding.js +47 -0
  81. package/opencode.js +74 -0
  82. package/package.json +153 -4
  83. package/plan-gate.js +92 -0
  84. package/preset-guard.js +47 -0
  85. package/request-evidence.js +41 -0
  86. package/roles.js +69 -0
  87. package/roster-defaults.js +70 -0
  88. package/route-health.js +39 -0
  89. package/runtime-observability.js +78 -0
  90. package/runtime-view.js +21 -0
  91. package/scripts/memory-cli.mjs +138 -0
  92. package/search-engines.js +119 -0
  93. package/search.js +164 -0
  94. package/session-policy.js +59 -0
  95. package/session-rotation.js +206 -0
  96. package/settings-schema.js +40 -0
  97. package/shared/managed-config.js +84 -0
  98. package/single-model.js +44 -0
  99. package/skills/project-role-theme/SKILL.md +46 -0
  100. package/task-bridge.js +32 -0
  101. package/task-control-api.js +36 -0
  102. package/task-control-audit.js +60 -0
  103. package/team.js +101 -0
  104. package/teammate-context.js +120 -0
  105. package/teammate-wait.js +18 -0
  106. package/template.js +46 -0
  107. package/theme-plugin.js +172 -0
  108. package/theme.js +115 -0
  109. package/token-discipline.js +116 -0
  110. package/token-metrics.js +37 -0
  111. package/tool-names.js +10 -0
  112. package/tool-schema.js +23 -0
  113. package/transcribe.js +104 -0
  114. package/ui.js +113 -0
  115. package/usage.js +92 -0
  116. package/workflow.js +246 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,156 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented in this file. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project adheres to
5
+ [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [Unreleased]
8
+
9
+ ### Added
10
+
11
+ - Mothership-managed config pages (Search 1.0.2, Memory 1.0.2, Autocomplete 1.0.1, Team 5.0.2): a page managed by an AIWUK Mothership opens with a "Configured by AIWUK Mothership" notice, shows the pushed non-secret values (URLs, models, engine lists) read-only, reports managed keys as "Configured by AIWUK Mothership", and folds integration settings the Mothership does not send under "Optional settings". The `api/aiwuk.managed-config.<ext>` route now carries `value` for non-secret refs only; secret refs (flagged or matching the forced-secret pattern) are never resolved or sent.
12
+ - `docs/standalone-setup.md` (and zh-CN): Mothership-managed vs. standalone setup for Search, Memory and Autocomplete, with an AI-agent checklist.
13
+ - `npm test` runs every test file with an isolated empty `DSH_HOME` (`tests/isolated-home.mjs`), so a developer's real managed-config file can no longer change test results.
14
+ - AIWUK Search settings page: "Use AIWUK Search for DSH web search" sets `searchProvider: aiwuk-search` on the host `web` entry through `configForms` (unset restores DSH's default) and shows the current provider.
15
+ - Config pages: a secret row whose state cannot be read now shows "Could not check" with a Retry button instead of waiting forever; one automatic retry runs first.
16
+ - Optional macOS desktop Mothership restart adapter and installer: preserve the native connector protocol, terminate the complete desktop app gracefully, and relaunch once after an authenticated restart request. Includes lifecycle tests and deployment/rollback instructions.
17
+
18
+ - New companion package `dsh-capability-autocomplete` 0.2.0 (`packages/autocomplete`): composer ghost-text suggestions for DSH Web, inline continuation while typing and next-message suggestions after a reply. Tab accepts, Alt+Up/Down cycles, Esc dismisses; nothing is sent automatically. Routes through the session model or a fixed provider/model.
19
+ - The owner-only `POST /autocomplete/predict` endpoint refuses cross-site requests, sends only user/assistant text as quoted context, caps concurrent predictions at four and never writes the transcript.
20
+ - Settings are read from the host ConfigForm (Settings → General and the plugin card) instead of browser storage, so the server and the client share one source of truth.
21
+ - `npm run build:autocomplete` generates the package's `client.js`; lint and build verify it is current and that every published file exists.
22
+ - `dsh-capability-autocomplete` 0.3.0: Chinese and English for the plugin name and description (`locale/zh.json`, `locale/en.json`), every settings field (schemastery `i18n`) and all on-screen text.
23
+ - Touch suggestion bar for phones and tablets: tappable suggestion buttons above the composer, a ✦ button to ask for a continuation, × to dismiss. Chosen by input type (`pointer: coarse`), switchable with `mobileBar`.
24
+ - `dsh-capability-autocomplete` 0.4.0: optional fleet defaults. A manager can push `AUTOCOMPLETE_PROVIDER`, `AUTOCOMPLETE_MODEL` (and optionally `AUTOCOMPLETE_ROUTE_MODE`) through the DSH credential store; they apply only while the user's own route is untouched, and the user's choice always wins as a whole.
25
+
26
+ ### Fixed
27
+
28
+ - Config pages (Team 5.0.3, Search 1.0.3, Memory 1.0.3, Autocomplete 1.0.2): a locked Mothership value now also drives dependent rows, so a Mothership-fixed autocomplete route shows its managed provider and model.
29
+ - Locked canonical managed credentials now override legacy/custom key aliases for their exact integration section and search engine. Missing locked credentials do not fall back to local aliases; standalone and unlocked configurations retain their existing behavior. Presence metadata reports the effective canonical ref, and both canonical credentials and ignored aliases remain covered by redaction. This shared resolver fix ships in Team 5.0.1, Search 1.0.1 and Memory 1.0.1.
30
+ - Config pages: secret rows could stay on "Checking..." although `credentials/describe` answered. Describe results are now ticketed (a stale or post-unmount result is ignored), a ref absent from a successful reply reads as not configured, a hung or failed call times out into the failure state, and the effect re-runs when the credentials service changes.
31
+ - Team overview on a draft session (before the first message): `/api/capability-team.runtime` and `/stream` answered 503. They now return 200 with an empty snapshot (epoch `unbound`) and an idle stream that switches to real snapshots once the runtime binds.
32
+ - READMEs no longer say a settings form is "coming"; they describe write-only secrets, Mothership lock badges and the web-search provider toggle.
33
+
34
+ ### Known host limitations
35
+
36
+ - DSH 0.2.2-rc.1 agent presets take only plain strings for `name` and `description` (`AgentPreset.Config` is `z.string()`, `PresetDefinition.name?: string`, and the preset picker renders them verbatim; only built-in presets get localized copy), so the AIWUK Team preset description stays English in the Chinese UI.
37
+
38
+ - `dsh-capability-autocomplete` 0.4.2: the touch suggestion strip spanned the whole composer column and started left of the input box. Its left and right edges now match the input box exactly, re-measured on resize.
39
+ - `dsh-capability-autocomplete` 0.4.1: on phones the suggestion strip was absolutely positioned over the area above the composer and covered the workspace and mode controls. It now sits in the composer dock (laid out in flow, no height while empty) and the ✦ moves into the composer tool row, or into the strip on screens narrower than 360 px where the tool row has no room. Suggestions also stand down in multi-agent sessions while a dispatch confirmation is docked or a teammate view is read-only.
40
+
41
+ - `dsh-capability-autocomplete` 0.3.1: the settings form (model routing, provider/model, candidates) was missing on instances where DSH is installed by pnpm, because the optional `@deepseek-ai/schemastery` peer could not be resolved from the profile and the plugin exported no `Config`. schemastery is now a direct dependency pinned to the version DSH ships.
42
+ - Autocomplete works across terminals: the client calls the endpoint relative to the page, and the endpoint accepts the forwarded host of the Mothership hub tunnel or a reverse proxy as same-origin, so phones and remote browsers are no longer refused as cross-origin.
43
+
44
+ ## [5.0.0] — AIWUK rebrand
45
+
46
+ ### Changed
47
+
48
+ - **BREAKING (package names):** the packages move to the `@aiwuk` scope. Loader ids, tool names, credential and
49
+ environment variable names and data directories are unchanged, so existing configuration carries over.
50
+ - `dsh-capability-team` 4.0.1 → `@aiwuk/team` 5.0.0 (the routing plugin is now `@aiwuk/team/roles`; patch id `capability-team-ui` unchanged)
51
+ - `dsh-capability-autocomplete` 0.4.2 → `@aiwuk/autocomplete` 1.0.0 (patch id `autocomplete` unchanged)
52
+ - `dsh-capability-memory` 0.3.0 → `@aiwuk/memory` 1.0.0 (patch id `global-memory` unchanged)
53
+ - `dsh-omni-search` 0.3.0 → `@aiwuk/search` 1.0.0 (patch id `omni-search` unchanged)
54
+ Steps and rollback: [docs/migration-aiwuk.md](docs/migration-aiwuk.md). The old names are not on the public npm registry; roll back from your original source (Hub tarball or link:).
55
+ - Display names: AIWUK Team / AI悟空 协作团队, AIWUK Autocomplete / AI悟空 输入补全, AIWUK Memory / AI悟空 全局记忆,
56
+ AIWUK Search / AI悟空 全能搜索 (plugin locale `meta.title` and `meta.description`).
57
+ - The client module ids are now the package names (`@aiwuk/team`, `@aiwuk/autocomplete`), as the host keys client
58
+ module rows by package name.
59
+ - All four packages declare `engines.dsh` `>=0.2.2-rc.1 <0.3.0`; the `@deepseek-ai/dsh-*` peer ranges are raised to
60
+ `>=0.2.2-rc.1`. The host evaluates `engines.dsh` alone, with prerelease versions included in the range check.
61
+
62
+ ### Added
63
+
64
+ - `brand.js`: `BRAND`, `EXTENSIONS` and `RENAME_MAP` as the single source for brand names, copied into the
65
+ `omni-search` and `global-memory` `shared/` closures by their `build-shared.mjs`.
66
+ - `tests/brand.test.mjs`: asserts the rename map, versions and engines, and that no old package name remains outside
67
+ the allowed places.
68
+ - Visual config pages on the DSH Plugins page (slot `plugins.bundle.config`) for Team, Memory, Search and
69
+ Autocomplete, generated from `shared-ui/`. API keys are set, replaced or cleared through the DSH credential store;
70
+ the page only shows whether a key is set and never reads the value back.
71
+ - Mothership-managed configuration: extensions read the values-free manifest the AIWUK Connector stores at
72
+ credential record `mothership-connector/managed-config` (file fallback `<DSH home>/aiwuk/managed-config.json`). A
73
+ locked ref wins over local settings and is shown read-only ("Managed by AIWUK Mothership"); an unlocked ref is a
74
+ default below local sources. Each extension serves the manifest at `GET /api/aiwuk.managed-config.<ext>`.
75
+ - `@aiwuk/search` registers a native DSH web search provider (`aiwuk-search`) and, when the experimental service is
76
+ present, a cloud speech-to-text provider (`aiwuk-cloud-stt`). The `omni_*` tools are kept. A native fetch provider
77
+ is deferred until the fetch path reports the real target status.
78
+ - Server-side i18n (`i18n.js`, `locale/server.*.json`): model-facing tool text is English; user-facing text follows
79
+ `AIWUK_LOCALE`, then the host `locale.preference`, then English.
80
+
81
+ ### Changed (UI)
82
+
83
+ - All client UI follows the DSH dark/light theme through `--dsw-*` host tokens; the stage's own palette,
84
+ `prefers-color-scheme` and the overview's private language toggle are removed. The pixel stage resolves token
85
+ colors for the canvas and redraws on `theme/change`.
86
+ - Autocomplete fleet route: each locked field applies on its own; an incomplete fixed route falls back to `session`.
87
+
88
+ ### Compatibility
89
+
90
+ - Verified by running the host's `evaluatePluginCompatibility` (`dsh-app-boot`, installed DSH 0.2.2-rc.1) on all four
91
+ manifests: compatible with 0.2.2-rc.1 and 0.2.2, incompatible with 0.2.1-rc.5 and 0.3.0. A full session on a clean
92
+ 0.2.2-rc.1 instance was not run: unverified.
93
+ - The first-run importer for personal installs is future work; use the documented commands for now.
94
+
95
+ ## [4.0.1] - 2026-11-05
96
+
97
+ ### Added
98
+
99
+ - Show dispatch confirmation above the composer and display the model each member actually requested.
100
+ - Let the Lead follow the conversation's selected model with same-tier fallback routing.
101
+ - Provide an external-agent memory CLI for explicit recall and writes.
102
+
103
+ ### Fixed
104
+
105
+ - Determine capability-session projections from the session header before the live agent preset.
106
+ - Publish the changes through source commit `8603890` under a new immutable package version instead of reusing 4.0.0.
107
+
108
+ ## [4.0.0] - 2026-11-03
109
+
110
+ ### Changed
111
+
112
+ - **BREAKING:** Packages are now published to npm under unscoped names. Update the package names in DSH
113
+ profiles, bundle lists and `cordis.patch.yml` overrides that referenced the old local names:
114
+ - `@local/dsh-capability-team` → `dsh-capability-team` (the routing plugin is now `dsh-capability-team/roles`)
115
+ - `@local/dsh-omni-search` → `dsh-omni-search` (0.3.0; patch id `omni-search` unchanged)
116
+ - `@local/dsh-global-memory` → `dsh-capability-memory` (0.3.0; patch id `global-memory` unchanged)
117
+ - **BREAKING:** The default roster is now neutral: `lead`, `architect` (technical-director), `backend`,
118
+ `frontend`, `researcher` (research), `writer` and `qa`. The Web client shows localized names (English / Chinese)
119
+ while a project keeps the default display names.
120
+ - Package descriptions are in English; packages declare `repository`, `homepage`, `bugs`, `author` and keywords.
121
+ - Published tarballs no longer include tests, test helpers or development scripts.
122
+
123
+ ### Added
124
+
125
+ - Optional built-in `three-kingdoms` roster theme (the former default heroes), applied per project with
126
+ `capability_team_import { theme: "three-kingdoms" }` and reverted with `theme: "default"`. Hero idle quotes are
127
+ shown only while that roster is active.
128
+ - Optional peer dependencies on the DSH host packages the bundles plug into (`@deepseek-ai/cordis >= 4.0.1`, DSH
129
+ packages `>= 0.2.1-rc.3`).
130
+ - `roster-defaults.js` holds the built-in rosters; `config.js` shrinks accordingly.
131
+ - Onboarding: when DSH's official Agent Teams bundle (`@deepseek-ai/dsh-experimental-agent-team-profile`) is not
132
+ running, the team overview shows a card that explains it is required, warns that enabling it affects every session
133
+ in the profile and disables the native generic subagent tools, and enables it only when the user clicks (DSH's
134
+ plugin manager; applied, restart-required or the failure code is shown). A three-step welcome (enable Agent Teams →
135
+ model tier coverage → new session with the Capability Team preset) appears once in DSH's first-run onboarding and
136
+ after the bundle is enabled on the Plugins page. `/capability-status` and `GET /api/capability-team.onboarding`
137
+ report `teamServicePresent`.
138
+ - Single-model mode: an explicit, user-confirmed project setting (`singleModel`, via `capability_team_save` with
139
+ `single_model: "use-default" | "off"`) that routes roles without an assigned model to DSH's current default model
140
+ instead of blocking them. No fallbacks; assigned roles keep their routes; role cards, role view and battle report
141
+ show a badge. The unassigned-role error now mentions this option.
142
+ - Settings forms: `dsh-omni-search` and `dsh-capability-memory` export a schemastery `Config` with live (volatile)
143
+ fields for engines, SearXNG/gateway URLs, key variable names, embeddings and memory backend, edited on their DSH
144
+ Plugins page. Set fields override credentials, environment and `integrations.json`; empty fields keep them.
145
+
146
+ ### Compatibility
147
+
148
+ - Project configurations saved before 4.0 that store any hero role (renamed, removed, or complete roles from a
149
+ template) keep the hero roster; their next save stores the roster explicitly (`roster: 2`).
150
+ - Projects that never saved a role keep no trace of the old default and show the neutral roster after upgrading;
151
+ ask the lead to apply theme `three-kingdoms` to restore the hero names.
152
+ - Teammates spawned under hero ids (for example `lv-bu`, `diao-chan-r2`) still resolve to the matching neutral
153
+ role's capability, model and policies when the project uses the neutral roster.
154
+
155
+ [Unreleased]: https://github.com/walke2019/dsh-capability-team/compare/v4.0.0...HEAD
156
+ [4.0.0]: https://github.com/walke2019/dsh-capability-team/releases/tag/v4.0.0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 walke2019
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,150 @@
1
- # Temporary Holding Version
1
+ # AIWUK Team for DSH
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ **Your AI team, each doing what it is best at: expensive models only take hard work, and one failing model never stops the team.**
4
+
5
+ [![CI](https://github.com/walke2019/dsh-capability-team/actions/workflows/ci.yml/badge.svg)](https://github.com/walke2019/dsh-capability-team/actions/workflows/ci.yml)
6
+ ![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)
7
+ ![DSH >= 0.2.2-rc.1](https://img.shields.io/badge/DSH-%3E%3D%200.2.2--rc.1-6f42c1.svg)
8
+ ![Node >= 22](https://img.shields.io/badge/Node-%3E%3D%2022-339933.svg)
9
+
10
+ **English** | [简体中文](README.zh-CN.md)
11
+
12
+ A multi-agent team preset for DeepSeek Harness (DSH). It is a thin enhancement layer on top of the official DSH Agent Teams (the `agentTeams` service, the `team_task_*` board, `spawn_teammate` / `send_message`), not a replacement for them.
13
+
14
+ <!-- TODO: docs/assets/demo.gif — lead dispatches, a model is unplugged, same-tier backup takes over -->
15
+
16
+ ## Why
17
+
18
+ - **Every teammate runs on your most expensive model → each role is routed to its own capability tier.** Architecture and review go to high/upper-tier models, docs and simple chores to cheaper ones. Teammates never inherit the lead's model, and a role with no qualifying model fails closed instead of silently borrowing one.
19
+ - **One provider outage or exhausted quota stalls the whole team → same-tier failover keeps it running.** After retries, the request moves to a configured backup of the same tier (never a cross-tier downgrade); auth and quota errors switch immediately, and failed routes cool down exponentially before returning.
20
+ - **Agents improvise, overwrite each other and burn tokens → a plan-first, board-driven workflow.** Every request starts with a dispatch plan (delegate / free / direct), subtasks land on the native task board with write scopes, and token discipline keeps prompts cacheable and outputs trimmed.
21
+
22
+ ## Quick start
23
+
24
+ Requirements: DSH >= 0.2.2-rc.1 (below 0.3.0), Node.js >= 22, and the official `@deepseek-ai/dsh-experimental-agent-team-profile` bundle enabled in your DSH profile.
25
+
26
+ **1. Install**
27
+
28
+ ```bash
29
+ dsh plugin add @aiwuk/team
30
+ # optional companion packages
31
+ dsh plugin add @aiwuk/search
32
+ dsh plugin add @aiwuk/memory
33
+ dsh plugin add @aiwuk/autocomplete
34
+ ```
35
+
36
+ **2. Enable Agent Teams from the onboarding card**
37
+
38
+ If the official Agent Teams bundle is not enabled, an onboarding card detects it and enables it only after you click the button. Note that this affects every session in the profile and disables DSH's native generic subagent tools. The card also shows which capabilities matched a model tier. If some cannot be matched, add a provider, or ask the lead to turn on single-model mode: roles without a match then use your DSH default model, after you confirm it in a dialog.
39
+
40
+ > If the card reports that this connection cannot manage plugins, enable `@deepseek-ai/dsh-experimental-agent-team-profile` on DSH's Plugins page instead, and restart DSH if asked.
41
+
42
+ **3. Start a team session**
43
+
44
+ New session → choose the **Capability Team** preset (`capability-team`) → ask:
45
+
46
+ ```text
47
+ Read the team configuration
48
+ ```
49
+
50
+ The lead reports the roster, the tier of each role and which of your models were matched.
51
+
52
+ ## Comparison
53
+
54
+ | | Single agent | Official DSH Agent Teams | This preset |
55
+ |---|---|---|---|
56
+ | Team lifecycle, messaging, task board | — | ✅ | ✅ (reuses the official ones) |
57
+ | Per-role model routing | — | No per-role choice; all teammates use the profile's spawn provider | ✅ Tier-based, auto-matched from your models; fails closed |
58
+ | Same-tier failover | — | — | ✅ With exponential cooldown, no cross-tier downgrade |
59
+ | Cost controls / budgets | — | Usage projections | ✅ Tier routing, per-role token budgets, token discipline |
60
+ | Dispatch triage | — | — | ✅ `delegate` / `free` / `direct` dispatch plan |
61
+ | Write-scope enforcement | — | — | ✅ Per-task `write_scopes`, per-role path/tool/sandbox policy |
62
+ | Workbench | — | Team UI | ✅ Overview, board, usage, model management, pixel office stage |
63
+
64
+ See [docs/comparison.md](docs/comparison.md) for a longer comparison, including generic model-routing plugins.
65
+
66
+ ## How it works
67
+
68
+ ```mermaid
69
+ flowchart LR
70
+ R[User request] --> P[Dispatch plan<br/>delegate / free / direct]
71
+ P --> T[Roles routed by tier<br/>high · upper · medium · low]
72
+ T --> B[Native task board<br/>write scopes + dependencies]
73
+ B --> V[Verify<br/>QA / architect review]
74
+ V --> D[Lead delivers]
75
+ T -. model fails .-> F[Same-tier backup<br/>+ cooldown]
76
+ F -.-> T
77
+ ```
78
+
79
+ - **Capability tiers.** Models registered in your DSH catalog are graded `high` / `upper` / `medium` / `low` by vendor naming rules; non-chat, free/trial and below-floor models are excluded. Each capability gets a primary and up to three same-tier backups.
80
+ - **Per-role routing.** Every role (capability) has its own tier and vendor preference. Unassigned roles fail closed.
81
+ - **Failover.** Same-route retries first, then the next same-tier backup. Failed routes cool down (doubling, capped at 30 minutes) and the primary returns after recovery. Auth and quota errors skip straight to the next backup.
82
+ - **Dispatch and board.** The lead must call `capability_dispatch_plan` first; delegated subtasks become board tasks with `write_scopes` and `blocked_by`, and edits outside a held task's scopes are rejected.
83
+ - **Guardrails.** Per-role sandbox / tool / path access, optional plan-first approval, session rotation with handoff, and token discipline (stable prompt prefix, output trimming, idle compaction).
84
+ - **Workbench and memory.** A live right-side panel shows the team overview, board, usage and model management with a pixel office stage; a local memory hub recalls past pitfalls and decisions for new tasks.
85
+
86
+ ## Configuration
87
+
88
+ Most settings are changed by asking the lead (the change is validated and shown in DSH's confirmation dialog before it is saved) or from the workbench. Tools: `capability_team_read`, `capability_team_save`, `capability_team_import`.
89
+
90
+ ```json
91
+ { "capabilities": { "writer": { "tier": "low", "search": false } } }
92
+ ```
93
+
94
+ Integrations (search, transcription, embeddings, shared memory) are read from `integrations.json` in `~/.dsh/capability-team-themes/` (`%USERPROFILE%/.dsh/capability-team-themes/` on Windows; override the directory with `CAPABILITY_TEAM_HOME`), host environment variables, or the DSH credential store. Never put literal keys in configuration — only environment variable names such as `OMNI_SEARCH_API_KEY`.
95
+
96
+ Visual settings pages for the companion packages are on DSH's **Plugins** page (open the plugin's entry; the Team page links to them):
97
+
98
+ - **Secrets are write-only**: keys show a state only (Not set / Set / Managed) and can be Set, Replaced or Cleared, never read back. They live in DSH's credential store.
99
+ - **Mothership lock badges**: settings managed by an AIWUK Mothership show "Managed by AIWUK Mothership" (locked) or "Default from Mothership" (editable).
100
+ - **Web-search provider toggle** (AIWUK Search page): "Use AIWUK Search for DSH web search" selects `aiwuk-search` for DSH's native `web_search`; turning it off restores DSH's default.
101
+
102
+ Search, Memory and Autocomplete setup (Mothership-managed vs. standalone, with an AI-agent checklist): [docs/standalone-setup.md](docs/standalone-setup.md). Full reference: [docs/configuration.md](docs/configuration.md) — tiers and auto-matching, permissions, budgets, plan-first, dispatch workflow, failover and long context, rotation, memory, and all environment variables.
103
+
104
+ ## Roster and themes
105
+
106
+ The default roster is neutral:
107
+
108
+ | Role id | Capability | Default tier |
109
+ |---|---|---|
110
+ | `lead` | orchestrator | high |
111
+ | `architect` | technical-director | upper |
112
+ | `backend` | backend | upper |
113
+ | `frontend` | frontend | medium |
114
+ | `researcher` | research (read-only) | medium |
115
+ | `writer` | writer (docs paths only) | low |
116
+ | `qa` | qa | upper |
117
+
118
+ Names, duties, tiers and models can be changed per project. The former Three Kingdoms roster is an optional built-in theme: ask the lead to apply theme `three-kingdoms` (`capability_team_import { theme: "three-kingdoms" }`) and switch back with theme `default`. Projects saved with 3.x that renamed or removed hero ids keep their hero roster.
119
+
120
+ ## Companion packages
121
+
122
+ | Package | What it adds |
123
+ |---|---|
124
+ | [`@aiwuk/search`](packages/omni-search/README.md) | `omni_search`, `omni_fetch`, `omni_search_providers`, `omni_transcribe` for all sessions. Vendor-neutral engines (Tavily, Brave, SearXNG, custom gateway) with failover; fetch works keyless via Jina Reader. |
125
+ | [`@aiwuk/memory`](packages/global-memory/README.md) | Explicit memory tools and bounded automatic recall. Local by default; optional OpenAI-compatible embeddings and an OpenViking shared backend. |
126
+ | [`@aiwuk/autocomplete`](packages/autocomplete/README.md) | Composer autocomplete for DSH Web: inline continuation while typing and next-message suggestions, accepted with Tab and never sent automatically. Follows the session model or a fixed low-latency model. |
127
+
128
+ All are independent profile bundles: they also work outside team sessions, and the team plugin's memory hub reads the same memory and search configuration.
129
+
130
+ ## Migration
131
+
132
+ **To AIWUK (5.0.0 / 1.0.0).** The packages are now published under the `@aiwuk` scope; Loader ids, tool names, credential variables and data directories are unchanged, so your configuration carries over. Steps and rollback: [docs/migration-aiwuk.md](docs/migration-aiwuk.md).
133
+
134
+ **From 3.x.** 4.0.0 contains breaking changes: the packages were renamed and the default roster is now neutral. Update profile, bundle-list and `cordis.patch.yml` references, then see [CHANGELOG.md](CHANGELOG.md) for details. The older `cpa-tools` → `omni-search` rename is covered in [docs/configuration.md](docs/configuration.md#migration-from-cpa-tools).
135
+
136
+ ## Roadmap
137
+
138
+ - Public benchmark report (cost and completion versus a single agent) — coming.
139
+ - Team templates for common project types.
140
+ - Mothership: multi-machine management — coming later.
141
+
142
+ ## Contributing
143
+
144
+ For macOS desktop instances using the AIWUK Mothership connector, see [managed desktop restart](docs/macos-desktop-restart.md). This optional deployment adapter preserves the native connector protocol and restarts the full desktop app through a local supervisor.
145
+
146
+ Issues and pull requests are welcome; please use the [issue templates](.github/ISSUE_TEMPLATE) and the [pull request template](.github/PULL_REQUEST_TEMPLATE.md). Read the [development guide](docs/development.md) and the repository rules in [AGENTS.md](AGENTS.md) first. Before submitting, run `npm test`, `npm run lint` and `npm run build`.
147
+
148
+ ## License
149
+
150
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,150 @@
1
+ # DSH AI悟空 协作团队
2
+
3
+ **你的 AI 团队,各展所长:贵的模型只干难活,一个模型挂了团队照常运转。**
4
+
5
+ [![CI](https://github.com/walke2019/dsh-capability-team/actions/workflows/ci.yml/badge.svg)](https://github.com/walke2019/dsh-capability-team/actions/workflows/ci.yml)
6
+ ![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)
7
+ ![DSH >= 0.2.2-rc.1](https://img.shields.io/badge/DSH-%3E%3D%200.2.2--rc.1-6f42c1.svg)
8
+ ![Node >= 22](https://img.shields.io/badge/Node-%3E%3D%2022-339933.svg)
9
+
10
+ [English](README.md) | **简体中文**
11
+
12
+ 面向 DeepSeek Harness(DSH)的多智能体团队预设。它是构建在官方 DSH Agent Teams(`agentTeams` 服务、`team_task_*` 看板、`spawn_teammate` / `send_message`)之上的一层轻量增强,而不是替代品。
13
+
14
+ <!-- TODO: docs/assets/demo.gif — lead dispatches, a model is unplugged, same-tier backup takes over -->
15
+
16
+ ## 为什么需要它
17
+
18
+ - **每个队友都在用你最贵的模型 → 每个角色按能力档位路由。** 架构与审查交给 high/upper 档模型,文档和简单杂活交给更便宜的模型。队友不会继承主控的模型;没有合格模型的角色直接 fail-closed,绝不悄悄借用别的模型。
19
+ - **一个渠道宕机或额度耗尽,整个团队卡住 → 同档容灾让团队继续运转。** 重试之后,请求切换到已配置的同档备选(绝不跨档降级);鉴权与额度错误立即切换,故障路由按指数冷却后再恢复。
20
+ - **智能体各自乱干、互相覆盖、浪费 Token → 计划先行、看板驱动的工作流。** 每个请求先出派活计划(delegate / free / direct),子任务带写入范围进入原生任务看板,Token 纪律保证提示词可缓存、输出被裁剪。
21
+
22
+ ## 快速开始
23
+
24
+ 前置条件:DSH >= 0.2.2-rc.1(且 < 0.3.0)、Node.js >= 22,并在 DSH profile 中启用官方 `@deepseek-ai/dsh-experimental-agent-team-profile` bundle。
25
+
26
+ **1. 安装**
27
+
28
+ ```bash
29
+ dsh plugin add @aiwuk/team
30
+ # 可选的配套包
31
+ dsh plugin add @aiwuk/search
32
+ dsh plugin add @aiwuk/memory
33
+ dsh plugin add @aiwuk/autocomplete
34
+ ```
35
+
36
+ **2. 在引导卡片中启用 Agent Teams**
37
+
38
+ 如果官方 Agent Teams bundle 未启用,引导卡片会检测到,只有你点击按钮后才会启用。注意:启用后会影响该 profile 下的所有会话,并停用 DSH 原生的通用子智能体工具。卡片还会显示哪些能力已匹配到模型档位。如果有能力匹配不到,可以补充渠道,或让主控开启单模型模式:未匹配的角色改用你的 DSH 默认模型,开启前会弹出确认框由你确认。
39
+
40
+ > 如果卡片提示当前连接不能管理插件,请在 DSH 的“插件”页手动启用 `@deepseek-ai/dsh-experimental-agent-team-profile`,并按提示重启 DSH。
41
+
42
+ **3. 开始团队会话**
43
+
44
+ 新建会话 → 选择 **能力协作团队** 预设(`capability-team`)→ 输入:
45
+
46
+ ```text
47
+ 读取团队配置
48
+ ```
49
+
50
+ 主控会汇报名册、各角色的档位以及匹配到了你的哪些模型。
51
+
52
+ ## 对比
53
+
54
+ | | 单智能体 | 官方 DSH Agent Teams | 本预设 |
55
+ |---|---|---|---|
56
+ | 团队生命周期、消息、任务看板 | — | ✅ | ✅(复用官方能力) |
57
+ | 按角色模型路由 | — | 不能按角色选模型,所有队友走 profile 配置的同一派生通道 | ✅ 按档位从你的模型自动匹配;未分配即 fail-closed |
58
+ | 同档容灾 | — | — | ✅ 指数冷却,绝不跨档降级 |
59
+ | 成本控制 / 预算 | — | 用量投影 | ✅ 档位路由、按角色 Token 预算、Token 纪律 |
60
+ | 派活分流 | — | — | ✅ `delegate` / `free` / `direct` 派活计划 |
61
+ | 写入范围强制 | — | — | ✅ 按任务 `write_scopes`,按角色路径/工具/沙箱策略 |
62
+ | 工作台 | — | 团队 UI | ✅ 总览、看板、用量、模型管理、像素办公室舞台 |
63
+
64
+ 更完整的对比(包括通用模型路由类插件)见 [docs/comparison.zh-CN.md](docs/comparison.zh-CN.md)。
65
+
66
+ ## 工作原理
67
+
68
+ ```mermaid
69
+ flowchart LR
70
+ R[用户请求] --> P[派活计划<br/>delegate / free / direct]
71
+ P --> T[按档位路由角色<br/>high · upper · medium · low]
72
+ T --> B[原生任务看板<br/>写入范围 + 依赖]
73
+ B --> V[验收<br/>QA / 架构师审查]
74
+ V --> D[主控交付]
75
+ T -. 模型故障 .-> F[同档备选<br/>+ 冷却]
76
+ F -.-> T
77
+ ```
78
+
79
+ - **能力档位。** DSH 目录中已注册的模型按厂商命名规则分为 `high` / `upper` / `medium` / `low`;非对话、免费/试用及低于版本下限的模型会被排除。每个能力获得一个主模型和最多三个同档备选。
80
+ - **按角色路由。** 每个角色(能力)有自己的档位与厂商偏好。未分配模型的角色 fail-closed。
81
+ - **容灾。** 先在同一路由重试,再切换到下一个同档备选。故障路由进入冷却(逐次加倍,上限 30 分钟),恢复后回到主模型。鉴权与额度错误直接跳到下一个备选。
82
+ - **派活与看板。** 主控必须先调用 `capability_dispatch_plan`;委派的子任务成为带 `write_scopes` 和 `blocked_by` 的看板任务,超出所持任务范围的修改会被拒绝。
83
+ - **护栏。** 按角色的沙箱/工具/路径权限、可选的计划先行审批、带交接的会话轮换,以及 Token 纪律(稳定提示词前缀、输出裁剪、空闲压缩)。
84
+ - **工作台与记忆。** 右侧实时面板展示团队总览、看板、用量与模型管理,并带像素办公室舞台;本地记忆枢纽在新任务时召回过去的踩坑与决策。
85
+
86
+ ## 配置
87
+
88
+ 大多数设置可以直接让主控修改(修改经校验,并在 DSH 确认对话框中展示差异后才保存),也可在工作台中操作。相关工具:`capability_team_read`、`capability_team_save`、`capability_team_import`。
89
+
90
+ ```json
91
+ { "capabilities": { "writer": { "tier": "low", "search": false } } }
92
+ ```
93
+
94
+ 集成(搜索、转写、嵌入、共享记忆)从 `~/.dsh/capability-team-themes/` 下的 `integrations.json`(Windows 为 `%USERPROFILE%/.dsh/capability-team-themes/`,可用 `CAPABILITY_TEAM_HOME` 覆盖目录)、Host 环境变量或 DSH 凭据库读取。配置中绝不写密钥本身,只写环境变量名,例如 `OMNI_SEARCH_API_KEY`。
95
+
96
+ 配套包的可视化设置页位于 DSH 的“插件”页(打开对应插件条目;团队页提供入口说明):
97
+
98
+ - **密钥只写不读**:密钥只显示状态(未设置 / 已设置 / 由母舰托管),可设置、替换或清除,但永远不会回显。密钥保存在 DSH 凭据库中。
99
+ - **母舰锁定徽标**:由 AI悟空 母舰托管的设置会显示“由 AI悟空 母舰托管”(锁定)或“母舰默认值”(可编辑)。
100
+ - **网页搜索提供方开关**(AI悟空 全能搜索页):“让 DSH 网页搜索使用 AI悟空 全能搜索”会为 DSH 原生 `web_search` 选定 `aiwuk-search`;关闭后恢复 DSH 默认。
101
+
102
+ 全能搜索、全局记忆、输入补全的配置(母舰托管与单独安装两种方式,含给 AI 智能体的配置指引):[docs/standalone-setup.zh-CN.md](docs/standalone-setup.zh-CN.md)。完整参考:[docs/configuration.zh-CN.md](docs/configuration.zh-CN.md) —— 档位与自动匹配、权限、预算、计划先行、派活工作流、容灾与长上下文、轮换、记忆以及全部环境变量。
103
+
104
+ ## 名册与主题
105
+
106
+ 默认名册为中性角色:
107
+
108
+ | 角色 id | 能力 | 默认档位 |
109
+ |---|---|---|
110
+ | `lead` | orchestrator | high |
111
+ | `architect` | technical-director | upper |
112
+ | `backend` | backend | upper |
113
+ | `frontend` | frontend | medium |
114
+ | `researcher` | research(只读) | medium |
115
+ | `writer` | writer(仅文档路径) | low |
116
+ | `qa` | qa | upper |
117
+
118
+ 名称、职责、档位和模型都可按项目修改。原先的三国名册现在是可选的内置主题:让主控应用主题 `three-kingdoms`(`capability_team_import { theme: "three-kingdoms" }`),用主题 `default` 切回。3.x 保存的项目若改名或删除了英雄 id,会保留其英雄名册。
119
+
120
+ ## 配套包
121
+
122
+ | 包 | 提供的能力 |
123
+ |---|---|
124
+ | [`@aiwuk/search`](packages/omni-search/README.md) | 为所有会话提供 `omni_search`、`omni_fetch`、`omni_search_providers`、`omni_transcribe`。厂商中立的引擎(Tavily、Brave、SearXNG、自定义网关)自动故障切换;抓取经 Jina Reader 免密钥可用。 |
125
+ | [`@aiwuk/memory`](packages/global-memory/README.md) | 显式记忆工具与有界自动召回。默认纯本地;可选 OpenAI 兼容嵌入与 OpenViking 共享后端。 |
126
+ | [`@aiwuk/autocomplete`](packages/autocomplete/README.md) | DSH Web 输入框自动补全:打字停顿时行内续写、回复后建议下一句,按 Tab 采纳、绝不自动发送。可跟随会话模型或固定一个低延迟模型。 |
127
+
128
+ 它们都是独立的 profile bundle:在团队会话之外同样可用,团队插件的记忆枢纽读取同一份记忆与搜索配置。
129
+
130
+ ## 迁移
131
+
132
+ **迁移到 AI悟空(5.0.0 / 1.0.0)。** 包已改发布到 `@aiwuk` 作用域;Loader id、工具名、凭据变量与数据目录均不变,原有配置会沿用。步骤与回滚见 [docs/migration-aiwuk.md](docs/migration-aiwuk.md)。
133
+
134
+ **从 3.x 迁移。** 4.0.0 包含破坏性变更:包已改名,默认名册改为中性角色。请更新 profile、bundle 列表和 `cordis.patch.yml` 中的引用,详情见 [CHANGELOG.md](CHANGELOG.md)。更早的 `cpa-tools` → `omni-search` 改名见 [docs/configuration.zh-CN.md](docs/configuration.zh-CN.md)。
135
+
136
+ ## 路线图
137
+
138
+ - 公开基准测试报告(与单智能体对比成本与完成度)—— 即将发布。
139
+ - 面向常见项目类型的团队模板。
140
+ - Mothership:多机管理 —— 稍后推出。
141
+
142
+ ## 参与贡献
143
+
144
+ macOS 桌面实例接入 AIWUK 中枢后,可按[桌面托管重启说明](docs/macos-desktop-restart.md)安装可选部署适配器;它复用连接器原生协议,通过本机监督器重启整个桌面应用。
145
+
146
+ 欢迎提交 Issue 和 Pull Request,请使用 [Issue 模板](.github/ISSUE_TEMPLATE) 和 [PR 模板](.github/PULL_REQUEST_TEMPLATE.md)。请先阅读[开发指南](docs/development.zh-CN.md)和仓库规范 [AGENTS.md](AGENTS.md)。提交前运行 `npm test`、`npm run lint` 和 `npm run build`。
147
+
148
+ ## 许可
149
+
150
+ MIT,详见 [LICENSE](LICENSE)。
package/access.js ADDED
@@ -0,0 +1,93 @@
1
+ import { m as modelText } from './i18n.js';
2
+ // Per-capability access policy for every team member: denied tools, shell, and the project paths that may be edited.
3
+ // The check is lexical (no filesystem I/O) and runs as a monotonic tools.guard, so no later listener can allow a denied call.
4
+ import { isAbsolute, relative, resolve } from 'node:path';
5
+ import { resolveRole } from './config.js';
6
+ import { checkFileLimits } from './file-limits.js';
7
+ import { restrictiveToolName } from './tool-names.js';
8
+ /** Tools that run arbitrary commands; a path restriction is meaningless while these stay available. */
9
+ export const SHELL_TOOLS = ['bash', 'pwsh', 'terminal_*'];
10
+ /** DSH file tools that modify files, with the argument holding the target path (dsh-tool-fs, dsh-tool-str-replace-editor). */
11
+ const EDIT_TOOLS = { write: 'file_path', edit: 'file_path', str_replace_editor: 'path' };
12
+ export const matchesTool = (name, rules) => rules.some(rule => rule.endsWith('*') ? name.startsWith(rule.slice(0, -1)) : name === rule);
13
+ const matchesRestrictedTool = (name, rules) => matchesTool(name, rules) || matchesTool(restrictiveToolName(name, n => matchesTool(n, rules)), rules);
14
+ /** Project-relative glob: `**` crosses directories, `*` and `?` stay inside one segment. */
15
+ export function globRegex(pattern) {
16
+ let out = '';
17
+ for (let i = 0; i < pattern.length; i++) {
18
+ const c = pattern[i];
19
+ if (c === '*' && pattern[i + 1] === '*') {
20
+ if (pattern[i + 2] === '/') { out += '(?:.*/)?'; i += 2; } else { out += '.*'; i += 1; }
21
+ } else if (c === '*') out += '[^/]*';
22
+ else if (c === '?') out += '[^/]';
23
+ else out += c.replace(/[.+^${}()|[\]\\]/g, '\\$&');
24
+ }
25
+ return new RegExp(`^${out}$`);
26
+ }
27
+ /** Target path of a file-modifying call, or undefined when the call does not modify a file. */
28
+ export function editTarget(exec) {
29
+ const name = restrictiveToolName(exec.name, EDIT_TOOLS);
30
+ const key = EDIT_TOOLS[name];
31
+ if (!key || (name === 'str_replace_editor' && exec.arguments?.command === 'view')) return undefined;
32
+ return typeof exec.arguments?.[key] === 'string' ? exec.arguments[key] : '';
33
+ }
34
+ /** Whether the session currently runs under the capability's DSH permission preset (sandbox and approval both match). */
35
+ export function presetActive(presets, session, access) {
36
+ if (!access?.preset || typeof presets?.current !== 'function') return false;
37
+ try { return presets.current(session) === access.preset; } catch { return false; }
38
+ }
39
+ /** Switch the session to the capability's DSH permission preset when it is not already in effect. */
40
+ export function ensurePreset(presets, session, access, log = () => {}) {
41
+ if (!access?.preset || typeof presets?.set !== 'function' || presetActive(presets, session, access)) return;
42
+ try { presets.set(session, access.preset); }
43
+ catch (error) { log(modelText('access.000', { 0: access.preset, 1: error.message ?? error })); }
44
+ }
45
+ /**
46
+ * Pure decision for one call under a capability's access policy; returns a denial reason or undefined.
47
+ * `sandboxed` means DSH's own permission preset is in effect, so its sandbox, not this guard, bounds commands.
48
+ */
49
+ export function checkAccess(exec, access, { project, cwd, label, sandboxed = false }) {
50
+ if (!access) return undefined;
51
+ if (access.deny.length && matchesRestrictedTool(exec.name, access.deny)) return modelText('access.001', { 0: label, 1: exec.name });
52
+ if (!access.shell && !sandboxed && matchesRestrictedTool(exec.name, SHELL_TOOLS)) return modelText('access.002', { 0: label, 1: access.preset ? modelText('access.003', { 0: access.preset }) : '' });
53
+ if (!access.editPaths) return undefined;
54
+ const target = editTarget(exec);
55
+ if (target === undefined) return undefined;
56
+ const scope = access.editPaths.length ? modelText('access.004', { 0: access.editPaths.join('、') }) : modelText('access.005');
57
+ if (!target || target.startsWith('~')) return modelText('access.006', { 0: label, 1: scope, 2: exec.name });
58
+ const rel = relative(project, resolve(cwd, target)).split('\\').join('/');
59
+ if (!rel || rel.startsWith('../') || rel === '..' || isAbsolute(rel)) return modelText('access.007', { 0: label, 1: scope, 2: target });
60
+ if (!access.editPaths.some(pattern => globRegex(pattern).test(rel))) return modelText('access.008', { 0: label, 1: scope, 2: rel });
61
+ return undefined;
62
+ }
63
+ /** Model-facing one-line summary of an access policy. */
64
+ export function accessSummary(access) {
65
+ if (!access) return undefined;
66
+ const parts = [];
67
+ if (access.editPaths) parts.push(access.editPaths.length ? modelText('access.009', { 0: access.editPaths.join('、') }) : modelText('access.010'));
68
+ if (access.preset) parts.push(modelText('access.011', { 0: access.preset, 1: access.shell ? '' : modelText('access.012') }));
69
+ else if (!access.shell) parts.push(modelText('access.013'));
70
+ if (access.deny.length) parts.push(modelText('access.014', { 0: access.deny.join('、') }));
71
+ return parts.length ? parts.join(';') : undefined;
72
+ }
73
+ import { ownsCapabilityTeam } from './preset-guard.js';
74
+ export function applyAccess(ctx, team) {
75
+ ctx.effect(() => ctx.tools.guard(exec => {
76
+ const membership = ctx.agentTeams.tryMembership(exec.agent);
77
+ if (!ownsCapabilityTeam(ctx, exec.agent) || membership === undefined) return undefined;
78
+ const project = team.pinned(exec.agent);
79
+ if (!project) return undefined; // Pinned before the first request; nothing can run earlier.
80
+ let role, effective;
81
+ try { effective = team.loadSync(project, exec.agent).effective; role = resolveRole(membership, effective); }
82
+ catch (error) {
83
+ // An unregistered teammate is already blocked at request routing; a broken config fails closed.
84
+ return /is not registered in this project's team configuration/.test(error.message) ? undefined : modelText('access.015', { 0: error.message });
85
+ }
86
+ const searchTools = new Set(['omni_search','omni_fetch','omni_search_providers','omni_transcribe','omni_transcribe_status']);
87
+ if (!role.search && searchTools.has(restrictiveToolName(exec.name, searchTools))) return modelText('access.016', { 0: role.capability });
88
+ const cwd = ctx.workingDirectory.get?.(exec.agent.session) ?? project;
89
+ const sandboxed = presetActive(ctx.get?.('permissionPresets'), exec.agent.session, role.access);
90
+ return checkAccess(exec, role.access, { project, cwd, sandboxed, label: modelText('access.017', { 0: role.displayName, 1: role.capability }) })
91
+ ?? checkFileLimits(exec, effective.workflow.fileLimits, { project, cwd, role: membership.role });
92
+ }));
93
+ }
@@ -0,0 +1,36 @@
1
+ import { t } from './i18n.js';
2
+ // Deliberately lossy labels: never copy tool text, descriptions, commands or results.
3
+ import path from 'node:path';
4
+ export const safeId = (value, max = 256) => typeof value === 'string' && value.length <= max && /^[\w.:-]+$/.test(value) ? value : null;
5
+ export const safeTime = value => Number.isSafeInteger(value) && value >= 0 ? value : null;
6
+ /** Path rules follow the session's own working directory, which is a Windows path on a Windows host (drive or UNC). */
7
+ const isWindowsPath = value => typeof value === 'string' && /^(?:[A-Za-z]:[\\/]|\\\\)/.test(value);
8
+ export function safeTarget(value, cwd, max = 240, root = cwd) {
9
+ if (typeof value !== 'string' || !value || value.length > 4096 || /[\x00-\x1f\x7f?#]/.test(value) || /:\/\//.test(value)) return null;
10
+ const windows = isWindowsPath(cwd);
11
+ const flavor = windows ? path.win32 : path.posix;
12
+ // A backslash separates segments only on Windows; on POSIX it is a literal character that could disguise a traversal.
13
+ if (!windows && value.includes('\\')) return null;
14
+ if (typeof cwd !== 'string' || !flavor.isAbsolute(cwd)) return null;
15
+ if (typeof root !== 'string' || !flavor.isAbsolute(root) || isWindowsPath(root) !== windows) return null;
16
+ const resolved = flavor.resolve(cwd, value);
17
+ const relative = flavor.relative(root, resolved).split(flavor.sep).join('/');
18
+ if (!relative || relative === '..' || relative.startsWith('../') || flavor.isAbsolute(relative)) return null;
19
+ if (relative.split('/').some(part => /^(\.env.*|\.ssh|\.aws|\.gnupg|\.?credentials.*|\.?secrets?.*|.*(?:api[-_]?key|token|password|credential).*|.*\.(pem|key|p12|pfx)|id_(rsa|ed25519).*)$/i.test(part))) return t('activity-summary.018');
20
+ return relative.length <= max ? relative : null;
21
+ }
22
+ export function summarizeActivity(tool, argumentsText, cwd, root = cwd) {
23
+ const name = safeId(tool, 80) ?? 'unknown';
24
+ const labels = { read: t('activity-summary.019'), read_image: t('activity-summary.020'), edit: t('activity-summary.021'), write: t('activity-summary.022'), grep: t('activity-summary.023'), glob: t('activity-summary.024'), bash: t('activity-summary.025') };
25
+ let target = null;
26
+ if (typeof argumentsText === 'string' && Buffer.byteLength(argumentsText) <= 16384) {
27
+ try {
28
+ const args = JSON.parse(argumentsText);
29
+ if (args && typeof args === 'object' && !Array.isArray(args)) {
30
+ const field = ['read', 'read_image', 'edit', 'write'].includes(name) ? 'file_path' : ['grep', 'glob'].includes(name) ? 'path' : null;
31
+ if (field) target = safeTarget(args[field], cwd, 240, root);
32
+ }
33
+ } catch { /* invalid input deliberately omitted */ }
34
+ }
35
+ return { tool: name, summary: labels[name] ?? t('activity-summary.026'), target };
36
+ }