@suifei/dsh-ai-team 0.3.4 → 0.3.6
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.
- package/CHANGELOG.md +13 -0
- package/README.md +4 -0
- package/client.js +109 -12
- package/icon.svg +28 -26
- package/index.js +6 -1
- package/lib/host/default-roster.js +71 -0
- package/lib/host/model-catalog.js +85 -0
- package/lib/host/project-runtime.js +2 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.6 — 2026-11-09
|
|
4
|
+
|
|
5
|
+
- 新图标:重檐阁楼 + 三开间柱 + 渊水纹 + 朱印,墨蓝 / 金 / 朱红;
|
|
6
|
+
- 桌面 profile 从 npm 安装 0.3.6,重启后加载。
|
|
7
|
+
|
|
8
|
+
## 0.3.5 — 2026-11-09
|
|
9
|
+
|
|
10
|
+
- 新 Project 默认成员:沈舟(主理人)/ 林岚(执行)/ 顾清(验收),均可在成员资料中改;
|
|
11
|
+
- 默认 Lead 成为 Project owner,让一句话开工可以直接唤醒团队;
|
|
12
|
+
- Provider / Model 改为读取 DSH `llm.listProviders()` + `llm.listModels()` 的下拉,不再手填 id;
|
|
13
|
+
- 新增 `GET /ai-team/api/models` 与 `lib/host/model-catalog.js`;
|
|
14
|
+
- 默认成员只在 Project 没有 Agent 时播种一次,不覆盖用户已有 roster。
|
|
15
|
+
|
|
3
16
|
## 0.3.4 — 2026-11-09
|
|
4
17
|
|
|
5
18
|
- README 精装修:品牌、快速开始、能力地图、架构图、API、验证证据、文档索引;
|
package/README.md
CHANGED
|
@@ -21,6 +21,8 @@
|
|
|
21
21
|
|
|
22
22
|
`@suifei/dsh-ai-team` 是 DeepSeek Harness 的按需启用插件:装进专用 `ai-team` profile 后,DSH 里出现一个三栏工作台——左侧 Project 与成员,中间主对话 / 看板 / 态势,右侧 Thread。Agent 自己认领 Task、互相交接,由做的人以外的成员验收;Human 只处理方向、信任和不可撤回的决定。
|
|
23
23
|
|
|
24
|
+
新 Project 自带一组可改的默认成员:**沈舟(主理人)**、**林岚(执行)**、**顾清(验收)**。名字、职责、个性、模型都可以改;模型提供商和模型名称直接读取 DSH 的 `llm` 目录,不需要手填 id。
|
|
25
|
+
|
|
24
26
|
---
|
|
25
27
|
|
|
26
28
|
## 为什么是「文渊阁」
|
|
@@ -98,7 +100,9 @@ dsh --profile ai-team --no-open --port 33271
|
|
|
98
100
|
| 验收 | maker 不能验收;QA 硬门禁;`qaChecklist` 逐项通过 |
|
|
99
101
|
| 收件箱 | Human/Agent 各自拉取;跨 Project 聚合与项目内 ack |
|
|
100
102
|
| 关注 | 关注 Task;完成后收到 `followed_task_update` |
|
|
103
|
+
| 默认成员 | 新 Project 初始化沈舟 / 林岚 / 顾清;可改名、改职责、改个性、改模型 |
|
|
101
104
|
| 成员 | 持久 session;名字/角色/personality/notes/skills |
|
|
105
|
+
| 模型 | Provider / Model 下拉读取 DSH `llm` 目录;可跟随系统默认 |
|
|
102
106
|
| 进化 | `team.evolve` 只改自己的 personality;验收后 reflection wake |
|
|
103
107
|
| 人才库 | leave/rejoin;遗留 Task 可被接管(taken-over) |
|
|
104
108
|
| 运行配方 | `standard` / `ptc` / `minimal` / `cordis` |
|
package/client.js
CHANGED
|
@@ -200,8 +200,14 @@ window.__ModuleLoader__.load({
|
|
|
200
200
|
presetPtc: 'PTC 模式',
|
|
201
201
|
presetMinimal: '极简模式',
|
|
202
202
|
presetCordis: '创造模式',
|
|
203
|
-
memberProvider: '
|
|
203
|
+
memberProvider: '模型提供商',
|
|
204
204
|
memberModel: '模型',
|
|
205
|
+
memberProviderDefault: '跟随系统默认',
|
|
206
|
+
memberModelDefault: '跟随系统默认',
|
|
207
|
+
memberNoProviders: 'DSH 暂未提供可用模型',
|
|
208
|
+
memberModelsLoading: '正在读取 DSH 模型目录…',
|
|
209
|
+
memberModelsError: '无法读取 DSH 模型目录,仍可使用系统默认模型。',
|
|
210
|
+
memberNoModels: '该提供商暂无模型',
|
|
205
211
|
memberOwner: '设为 Project owner',
|
|
206
212
|
memberCreate: '创建并激活',
|
|
207
213
|
memberAdding: '正在创建…',
|
|
@@ -468,8 +474,14 @@ window.__ModuleLoader__.load({
|
|
|
468
474
|
presetPtc: 'PTC',
|
|
469
475
|
presetMinimal: 'Minimal',
|
|
470
476
|
presetCordis: 'Creative',
|
|
471
|
-
memberProvider: '
|
|
477
|
+
memberProvider: 'Model provider',
|
|
472
478
|
memberModel: 'Model',
|
|
479
|
+
memberProviderDefault: 'Follow system default',
|
|
480
|
+
memberModelDefault: 'Follow system default',
|
|
481
|
+
memberNoProviders: 'DSH exposes no models yet',
|
|
482
|
+
memberModelsLoading: 'Reading the DSH model directory…',
|
|
483
|
+
memberModelsError: 'Could not read the DSH model directory; system default is still available.',
|
|
484
|
+
memberNoModels: 'No models for this provider',
|
|
473
485
|
memberOwner: 'Make Project owner',
|
|
474
486
|
memberCreate: 'Create and activate',
|
|
475
487
|
memberAdding: 'Creating…',
|
|
@@ -1088,6 +1100,8 @@ window.__ModuleLoader__.load({
|
|
|
1088
1100
|
.at-field { display: flex; flex-direction: column; gap: 4px; min-width: 0; color: var(--dsw-alias-label-tertiary); font-size: 11px; line-height: 16px; }
|
|
1089
1101
|
.at-field input, .at-field textarea, .at-field select { width: 100%; padding: 6px 8px; border: 1px solid var(--dsw-alias-border-l2); border-radius: var(--dsw-radius-sm); background: var(--dsw-alias-bg-base); color: var(--dsw-alias-label-primary); font: inherit; font-size: 12px; line-height: 18px; }
|
|
1090
1102
|
.at-field textarea { min-height: 64px; resize: vertical; }
|
|
1103
|
+
.at-modelfields { display: flex; flex-direction: column; gap: 4px; }
|
|
1104
|
+
.at-fieldhint { color: var(--dsw-alias-label-tertiary); font-size: 11px; line-height: 16px; }
|
|
1091
1105
|
.at-fieldrow { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
|
|
1092
1106
|
.at-modalback { position: fixed; inset: 0; z-index: 80; display: flex; align-items: center; justify-content: center; padding: 20px; background: color-mix(in srgb, var(--dsw-alias-bg-base) 72%, transparent); }
|
|
1093
1107
|
.at-modal { width: min(420px, 100%); max-height: 86vh; overflow-y: auto; display: flex; flex-direction: column; gap: 10px; padding: 16px; border: 1px solid var(--dsw-alias-border-l1); border-radius: 12px; background: var(--dsw-alias-bg-layer-2); }
|
|
@@ -1842,6 +1856,56 @@ window.__ModuleLoader__.load({
|
|
|
1842
1856
|
}
|
|
1843
1857
|
}
|
|
1844
1858
|
|
|
1859
|
+
function ModelFields({ t, catalog, provider, model, onProvider, onModel, error }) {
|
|
1860
|
+
const providers = Array.isArray(catalog?.providers) ? catalog.providers.slice() : [];
|
|
1861
|
+
if (provider !== '' && !providers.some((entry) => entry.id === provider)) {
|
|
1862
|
+
providers.unshift({ id: provider, name: provider, models: model === '' ? [] : [{ id: model, name: model }], modelCount: model === '' ? 0 : 1 });
|
|
1863
|
+
}
|
|
1864
|
+
const selected = providers.find((entry) => entry.id === provider);
|
|
1865
|
+
const models = Array.isArray(selected?.models) ? selected.models.slice() : [];
|
|
1866
|
+
if (model !== '' && !models.some((entry) => entry.id === model)) models.unshift({ id: model, name: model });
|
|
1867
|
+
const fallbackProvider = catalog?.defaultModel?.provider ?? '';
|
|
1868
|
+
const fallbackModel = catalog?.defaultModel?.model ?? '';
|
|
1869
|
+
const chooseProvider = (event) => {
|
|
1870
|
+
const next = event.target.value;
|
|
1871
|
+
onProvider(next);
|
|
1872
|
+
if (next === '') {
|
|
1873
|
+
onModel('');
|
|
1874
|
+
return;
|
|
1875
|
+
}
|
|
1876
|
+
if (fallbackProvider === next && fallbackModel !== '') {
|
|
1877
|
+
onModel(fallbackModel);
|
|
1878
|
+
return;
|
|
1879
|
+
}
|
|
1880
|
+
const entry = providers.find((candidate) => candidate.id === next);
|
|
1881
|
+
onModel(entry?.models?.[0]?.id ?? '');
|
|
1882
|
+
};
|
|
1883
|
+
const hint = error
|
|
1884
|
+
? error
|
|
1885
|
+
: catalog === null
|
|
1886
|
+
? t('memberModelsLoading')
|
|
1887
|
+
: providers.length === 0
|
|
1888
|
+
? t('memberNoProviders')
|
|
1889
|
+
: provider !== '' && models.length === 0
|
|
1890
|
+
? t('memberNoModels')
|
|
1891
|
+
: '';
|
|
1892
|
+
return h('div', { className: 'at-modelfields' },
|
|
1893
|
+
h('div', { className: 'at-fieldrow' },
|
|
1894
|
+
h('label', { className: 'at-field' }, h('span', null, t('memberProvider')),
|
|
1895
|
+
h('select', { value: provider, onChange: chooseProvider },
|
|
1896
|
+
h('option', { value: '' }, t('memberProviderDefault')),
|
|
1897
|
+
providers.map((entry) => h('option', { key: entry.id, value: entry.id }, entry.name + (entry.modelCount ? ' · ' + entry.modelCount : ''))))),
|
|
1898
|
+
h('label', { className: 'at-field' }, h('span', null, t('memberModel')),
|
|
1899
|
+
h('select', {
|
|
1900
|
+
value: model,
|
|
1901
|
+
disabled: provider === '' || models.length === 0,
|
|
1902
|
+
onChange: (event) => onModel(event.target.value),
|
|
1903
|
+
},
|
|
1904
|
+
h('option', { value: '' }, t('memberModelDefault')),
|
|
1905
|
+
models.map((entry) => h('option', { key: entry.id, value: entry.id }, entry.name === entry.id ? entry.id : entry.name + ' · ' + entry.id))))),
|
|
1906
|
+
hint ? h('div', { className: 'at-fieldhint' }, hint) : null);
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1845
1909
|
function Workbench({ t, ctx }) {
|
|
1846
1910
|
const [phase, setPhase] = React.useState('loading');
|
|
1847
1911
|
const [data, setData] = React.useState(null);
|
|
@@ -1870,6 +1934,8 @@ window.__ModuleLoader__.load({
|
|
|
1870
1934
|
const [mPrompt, setMPrompt] = React.useState('');
|
|
1871
1935
|
const [mProvider, setMProvider] = React.useState('');
|
|
1872
1936
|
const [mModel, setMModel] = React.useState('');
|
|
1937
|
+
const [modelCatalog, setModelCatalog] = React.useState(null);
|
|
1938
|
+
const [modelCatalogError, setModelCatalogError] = React.useState('');
|
|
1873
1939
|
const [mOwner, setMOwner] = React.useState(false);
|
|
1874
1940
|
const [mBusy, setMBusy] = React.useState(false);
|
|
1875
1941
|
const [mError, setMError] = React.useState('');
|
|
@@ -1949,6 +2015,28 @@ window.__ModuleLoader__.load({
|
|
|
1949
2015
|
};
|
|
1950
2016
|
}, [projectId]);
|
|
1951
2017
|
|
|
2018
|
+
React.useEffect(() => {
|
|
2019
|
+
let alive = true;
|
|
2020
|
+
fetch('/ai-team/api/models', { headers: { accept: 'application/json' } })
|
|
2021
|
+
.then(async (response) => {
|
|
2022
|
+
const body = await response.json().catch(() => null);
|
|
2023
|
+
if (!response.ok) throw new Error(body?.error?.message ?? ('HTTP ' + response.status));
|
|
2024
|
+
return body;
|
|
2025
|
+
})
|
|
2026
|
+
.then((body) => {
|
|
2027
|
+
if (!alive) return;
|
|
2028
|
+
setModelCatalog({
|
|
2029
|
+
defaultModel: body?.defaultModel ?? null,
|
|
2030
|
+
providers: Array.isArray(body?.providers) ? body.providers : [],
|
|
2031
|
+
});
|
|
2032
|
+
if (body?.ok === false) setModelCatalogError(body?.error?.message ?? t('memberModelsError'));
|
|
2033
|
+
})
|
|
2034
|
+
.catch((error) => {
|
|
2035
|
+
if (alive) setModelCatalogError(error?.message ?? t('memberModelsError'));
|
|
2036
|
+
});
|
|
2037
|
+
return () => { alive = false; };
|
|
2038
|
+
}, []);
|
|
2039
|
+
|
|
1952
2040
|
React.useEffect(() => {
|
|
1953
2041
|
if (!data) return;
|
|
1954
2042
|
if (!data.projects.some((project) => project.id === projectId)) {
|
|
@@ -2175,7 +2263,7 @@ window.__ModuleLoader__.load({
|
|
|
2175
2263
|
return body;
|
|
2176
2264
|
}
|
|
2177
2265
|
function openAddAgent() {
|
|
2178
|
-
const defaults = data?.runtime?.defaultModel ?? {};
|
|
2266
|
+
const defaults = modelCatalog?.defaultModel ?? data?.runtime?.defaultModel ?? {};
|
|
2179
2267
|
setMName('');
|
|
2180
2268
|
setMRole('');
|
|
2181
2269
|
setMPrompt('');
|
|
@@ -2780,11 +2868,15 @@ window.__ModuleLoader__.load({
|
|
|
2780
2868
|
onChange: (event) => setMPrompt(event.target.value),
|
|
2781
2869
|
placeholder: t('memberPromptHint'),
|
|
2782
2870
|
})),
|
|
2783
|
-
h(
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2871
|
+
h(ModelFields, {
|
|
2872
|
+
t,
|
|
2873
|
+
catalog: modelCatalog,
|
|
2874
|
+
provider: mProvider,
|
|
2875
|
+
model: mModel,
|
|
2876
|
+
onProvider: setMProvider,
|
|
2877
|
+
onModel: setMModel,
|
|
2878
|
+
error: modelCatalogError,
|
|
2879
|
+
}),
|
|
2788
2880
|
h('label', { className: 'at-check' },
|
|
2789
2881
|
h('input', { type: 'checkbox', checked: mOwner, onChange: (event) => setMOwner(event.target.checked) }),
|
|
2790
2882
|
h('span', null, t('memberOwner'))),
|
|
@@ -2826,12 +2918,17 @@ window.__ModuleLoader__.load({
|
|
|
2826
2918
|
h('option', { value: 'ptc' }, t('presetPtc')),
|
|
2827
2919
|
h('option', { value: 'minimal' }, t('presetMinimal')),
|
|
2828
2920
|
h('option', { value: 'cordis' }, t('presetCordis')))),
|
|
2829
|
-
h('label', { className: 'at-field' }, h('span', null, t('memberProvider')),
|
|
2830
|
-
h('input', { value: pProvider, onChange: (event) => setPProvider(event.target.value), placeholder: 'deepseek-account' })),
|
|
2831
|
-
h('label', { className: 'at-field' }, h('span', null, t('memberModel')),
|
|
2832
|
-
h('input', { value: pModel, onChange: (event) => setPModel(event.target.value), placeholder: 'deepseek-flash' })),
|
|
2833
2921
|
h('label', { className: 'at-field' }, h('span', null, t('memberMaxTokens')),
|
|
2834
2922
|
h('input', { value: pMaxTokens, onChange: (event) => setPMaxTokens(event.target.value), inputMode: 'numeric' }))),
|
|
2923
|
+
h(ModelFields, {
|
|
2924
|
+
t,
|
|
2925
|
+
catalog: modelCatalog,
|
|
2926
|
+
provider: pProvider,
|
|
2927
|
+
model: pModel,
|
|
2928
|
+
onProvider: setPProvider,
|
|
2929
|
+
onModel: setPModel,
|
|
2930
|
+
error: modelCatalogError,
|
|
2931
|
+
}),
|
|
2835
2932
|
h('label', { className: 'at-field' }, h('span', null, t('successor')),
|
|
2836
2933
|
h('select', {
|
|
2837
2934
|
value: pSuccessor,
|
package/icon.svg
CHANGED
|
@@ -1,27 +1,29 @@
|
|
|
1
|
-
|
|
2
|
-
<!--
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
<
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
<!--
|
|
22
|
-
<
|
|
23
|
-
stroke="
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64" role="img" aria-label="文渊阁图标">
|
|
2
|
+
<!-- 墨蓝底圆 -->
|
|
3
|
+
<circle cx="32" cy="32" r="31" fill="#17263A"/>
|
|
4
|
+
<circle cx="32" cy="32" r="28" fill="none" stroke="#D4AF37" stroke-width="1" opacity="0.55"/>
|
|
5
|
+
|
|
6
|
+
<!-- 重檐屋顶 -->
|
|
7
|
+
<path d="M23 21 L32 15 L41 21 Z" fill="#D4AF37"/>
|
|
8
|
+
<path d="M20 23 L44 23" stroke="#D4AF37" stroke-width="2" stroke-linecap="round"/>
|
|
9
|
+
<path d="M19 28 L32 23 L45 28 Z" fill="#D4AF37"/>
|
|
10
|
+
<path d="M16 30 L48 30" stroke="#D4AF37" stroke-width="2" stroke-linecap="round"/>
|
|
11
|
+
|
|
12
|
+
<!-- 三开间柱 -->
|
|
13
|
+
<path d="M22 31 V42 M28 31 V42 M36 31 V42 M42 31 V42"
|
|
14
|
+
stroke="#D4AF37" stroke-width="2" stroke-linecap="round"/>
|
|
15
|
+
|
|
16
|
+
<!-- 基座与台阶 -->
|
|
17
|
+
<path d="M19 43 H45" stroke="#D4AF37" stroke-width="2" stroke-linecap="round"/>
|
|
18
|
+
<path d="M17 46 H47" stroke="#D4AF37" stroke-width="2" stroke-linecap="round"/>
|
|
19
|
+
<path d="M21 49 H43" stroke="#D4AF37" stroke-width="1.5" stroke-linecap="round" opacity="0.7"/>
|
|
20
|
+
|
|
21
|
+
<!-- 渊水纹 -->
|
|
22
|
+
<path d="M15 54 Q21 51 27 54 T39 54 T51 54"
|
|
23
|
+
stroke="#D4AF37" stroke-width="1.5" fill="none" stroke-linecap="round" opacity="0.85"/>
|
|
24
|
+
<path d="M19 57 Q25 54 31 57 T43 57"
|
|
25
|
+
stroke="#D4AF37" stroke-width="1.5" fill="none" stroke-linecap="round" opacity="0.5"/>
|
|
26
|
+
|
|
27
|
+
<!-- 朱印:点“文”与“批红” -->
|
|
28
|
+
<rect x="29" y="33" width="6" height="7" rx="1" fill="#C0392B"/>
|
|
27
29
|
</svg>
|
package/index.js
CHANGED
|
@@ -24,6 +24,7 @@ import { installFileSkillProvider } from './lib/runtime/skill-provider.js';
|
|
|
24
24
|
import { apply as applySkillOpt } from './lib/vendor/skillopt/src/index.js';
|
|
25
25
|
import { createMemberPermissionGuard } from './lib/host/permission-guard.js';
|
|
26
26
|
import { installFaultAdapter } from './lib/host/fault-adapter.js';
|
|
27
|
+
import { buildModelCatalog } from './lib/host/model-catalog.js';
|
|
27
28
|
import { createTeamTool } from './lib/host/member-tools.js';
|
|
28
29
|
import { describeWake, planWakes } from './lib/host/waker.js';
|
|
29
30
|
|
|
@@ -32,7 +33,7 @@ export const name = 'ai-team';
|
|
|
32
33
|
/** The workbench only makes sense where the Web carrier exists. */
|
|
33
34
|
export const inject = ['webServer', 'permissionPresets', 'sandboxPolicy'];
|
|
34
35
|
|
|
35
|
-
const VERSION = '0.3.
|
|
36
|
+
const VERSION = '0.3.6';
|
|
36
37
|
const PROJECT_ID = 'prj_main';
|
|
37
38
|
const HUMAN_ID = 'mem_human';
|
|
38
39
|
|
|
@@ -592,6 +593,10 @@ export function apply(ctx) {
|
|
|
592
593
|
get: await skillsService.get('distilly', {}).then((skill) => skill?.name ?? null).catch(() => null),
|
|
593
594
|
});
|
|
594
595
|
}
|
|
596
|
+
if (req.method === 'GET' && url.pathname === '/ai-team/api/models') {
|
|
597
|
+
const catalog = await buildModelCatalog(ctx.get('llm'), ctx.get('agentDefaultModel'));
|
|
598
|
+
return send(res, catalog);
|
|
599
|
+
}
|
|
595
600
|
if (req.method === 'GET' && url.pathname === '/ai-team/api/health') {
|
|
596
601
|
const head = booted.ledger.head;
|
|
597
602
|
return send(res, {
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The initial 文渊阁 roster. These are real Members in the Project ledger, not
|
|
3
|
+
* UI placeholders: the Human can rename/re-role/re-model them, and the Lead is
|
|
4
|
+
* the default Project owner so a one-line Main-stream request can start work.
|
|
5
|
+
*
|
|
6
|
+
* Seeded only when a Project has no Agent member yet. Once a member exists the
|
|
7
|
+
* user's roster is authoritative and boot never adds these people back.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export const DEFAULT_MEMBER_ROSTER = Object.freeze([
|
|
11
|
+
{
|
|
12
|
+
id: 'mem_lead',
|
|
13
|
+
name: '沈舟',
|
|
14
|
+
role: '主理人',
|
|
15
|
+
personality: '先定义可验收的结果,再把方向拆成 Task;不替执行者写实现,只在关键时刻做决定。',
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
id: 'mem_maker',
|
|
19
|
+
name: '林岚',
|
|
20
|
+
role: '执行',
|
|
21
|
+
personality: '认领 Task 后直接推进,先交付可运行结果和证据再汇报;遇到阻塞先找 owner。',
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
id: 'mem_check',
|
|
25
|
+
name: '顾清',
|
|
26
|
+
role: '验收',
|
|
27
|
+
personality: '独立验收,只按结果定义和证据判断;不确定就退回,不与执行者共用 claim。',
|
|
28
|
+
},
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
export const DEFAULT_LEAD_ID = 'mem_lead';
|
|
32
|
+
|
|
33
|
+
function agentMembers(service) {
|
|
34
|
+
return Object.values(service.state.members ?? {}).filter((member) => member.kind === 'agent');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Add the default roster to an empty Project and make the Lead its owner.
|
|
39
|
+
* @returns {Promise<{seeded: boolean, members: string[]}>}
|
|
40
|
+
*/
|
|
41
|
+
export async function seedDefaultRoster(service, projectId, { humanId = 'mem_human' } = {}) {
|
|
42
|
+
if (agentMembers(service).length > 0) return { seeded: false, members: [] };
|
|
43
|
+
const added = [];
|
|
44
|
+
for (const member of DEFAULT_MEMBER_ROSTER) {
|
|
45
|
+
await service.dispatch({
|
|
46
|
+
type: 'member.add',
|
|
47
|
+
actor: { kind: 'human', memberId: humanId },
|
|
48
|
+
projectId,
|
|
49
|
+
payload: {
|
|
50
|
+
id: member.id,
|
|
51
|
+
name: member.name,
|
|
52
|
+
role: member.role,
|
|
53
|
+
personality: member.personality,
|
|
54
|
+
kind: 'agent',
|
|
55
|
+
status: 'idle',
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
added.push(member.id);
|
|
59
|
+
}
|
|
60
|
+
const project = service.state.projects?.[projectId];
|
|
61
|
+
const owner = project?.ownerMemberId ?? project?.ownerId;
|
|
62
|
+
if (owner === humanId || owner === undefined || owner === null) {
|
|
63
|
+
await service.dispatch({
|
|
64
|
+
type: 'project.owner.update',
|
|
65
|
+
actor: { kind: 'human', memberId: humanId },
|
|
66
|
+
projectId,
|
|
67
|
+
payload: { ownerMemberId: DEFAULT_LEAD_ID, reason: 'seed default roster' },
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
return { seeded: true, members: added };
|
|
71
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read the model directory from the live DSH `llm` service. The workbench must
|
|
3
|
+
* never ask the user to type provider/model ids: those ids already exist in the
|
|
4
|
+
* Host and can change when accounts/adapters are configured.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
function safeDefault(agentDefaultModel) {
|
|
8
|
+
try {
|
|
9
|
+
const selection = agentDefaultModel?.currentSelection?.();
|
|
10
|
+
if (selection === null || typeof selection !== 'object') return null;
|
|
11
|
+
if (typeof selection.provider !== 'string' || typeof selection.model !== 'string') return null;
|
|
12
|
+
return { provider: selection.provider, model: selection.model };
|
|
13
|
+
} catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function modelRows(models) {
|
|
19
|
+
const seen = new Set();
|
|
20
|
+
const rows = [];
|
|
21
|
+
for (const model of Array.isArray(models) ? models : []) {
|
|
22
|
+
if (model === null || typeof model !== 'object' || typeof model.id !== 'string' || model.id === '') continue;
|
|
23
|
+
if (seen.has(model.id)) continue;
|
|
24
|
+
seen.add(model.id);
|
|
25
|
+
rows.push({
|
|
26
|
+
id: model.id,
|
|
27
|
+
name: typeof model.name === 'string' && model.name !== '' ? model.name : model.id,
|
|
28
|
+
...(typeof model.description === 'string' && model.description !== '' ? { description: model.description } : {}),
|
|
29
|
+
...(Array.isArray(model.inputModalities) ? { inputModalities: model.inputModalities } : {}),
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
return rows;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @param {any} llm live DSH llm service
|
|
37
|
+
* @param {any} agentDefaultModel live DSH default-model service
|
|
38
|
+
*/
|
|
39
|
+
export async function buildModelCatalog(llm, agentDefaultModel) {
|
|
40
|
+
const defaultModel = safeDefault(agentDefaultModel);
|
|
41
|
+
if (llm === undefined || llm === null || typeof llm.listProviders !== 'function') {
|
|
42
|
+
return {
|
|
43
|
+
ok: false,
|
|
44
|
+
defaultModel,
|
|
45
|
+
providers: [],
|
|
46
|
+
error: { code: 'E_RUNTIME_UNAVAILABLE', message: 'the DSH llm service is unavailable' },
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
let providers;
|
|
50
|
+
try {
|
|
51
|
+
providers = llm.listProviders();
|
|
52
|
+
} catch (error) {
|
|
53
|
+
return {
|
|
54
|
+
ok: false,
|
|
55
|
+
defaultModel,
|
|
56
|
+
providers: [],
|
|
57
|
+
error: { code: 'E_MODEL_DIRECTORY', message: error?.message ?? String(error) },
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
const rows = await Promise.all((Array.isArray(providers) ? providers : []).map(async (provider) => {
|
|
61
|
+
const id = typeof provider?.id === 'string' ? provider.id : '';
|
|
62
|
+
const name = typeof provider?.name === 'string' && provider.name !== '' ? provider.name : id;
|
|
63
|
+
if (id === '') return null;
|
|
64
|
+
try {
|
|
65
|
+
const models = modelRows(await llm.listModels(id));
|
|
66
|
+
return { id, name, models, modelCount: models.length };
|
|
67
|
+
} catch (error) {
|
|
68
|
+
return {
|
|
69
|
+
id,
|
|
70
|
+
name,
|
|
71
|
+
models: [],
|
|
72
|
+
modelCount: 0,
|
|
73
|
+
error: error?.message ?? String(error),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
}));
|
|
77
|
+
const available = rows.filter((row) => row !== null);
|
|
78
|
+
return {
|
|
79
|
+
ok: true,
|
|
80
|
+
defaultModel,
|
|
81
|
+
providers: available,
|
|
82
|
+
providerCount: available.length,
|
|
83
|
+
modelCount: available.reduce((total, row) => total + row.models.length, 0),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import { mkdirSync } from 'node:fs';
|
|
11
11
|
import { join } from 'node:path';
|
|
12
12
|
import { openLedger } from '../ledger/writer.js';
|
|
13
|
+
import { seedDefaultRoster } from './default-roster.js';
|
|
13
14
|
import { TeamService } from './service.js';
|
|
14
15
|
|
|
15
16
|
export const DEFAULT_PROJECT_ID = 'prj_main';
|
|
@@ -59,6 +60,7 @@ export class ProjectRuntime {
|
|
|
59
60
|
payload: { id: projectId, name: projectName, goal: projectGoal },
|
|
60
61
|
});
|
|
61
62
|
}
|
|
63
|
+
await seedDefaultRoster(service, projectId, { humanId: HUMAN_ID });
|
|
62
64
|
}
|
|
63
65
|
return runtime;
|
|
64
66
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@suifei/dsh-ai-team",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.6",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "文渊阁 (Wenyuan Pavilion) — AI Team mode for DeepSeek Harness: concurrent projects, owned tasks, threads, inbox and an append-only team ledger",
|
|
6
6
|
"license": "MIT",
|