dsh-plugin-t-expert 0.3.24 → 0.3.26
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/README.md +9 -7
- package/data/.t-team-snapshot.json +10 -0
- package/data/experts/engineering/engineering-security-engineer.md +303 -0
- package/data/experts/engineering/engineering-threat-detection-engineer.md +553 -0
- package/data/experts/finance/finance-financial-forecaster.md +213 -0
- package/data/experts/hr/hr-recruiter.md +232 -0
- package/data/experts/marketing/marketing-search-growth-orchestrator.md +372 -0
- package/data/experts/specialized/specialized-pricing-optimizer.md +254 -0
- package/data/experts/support/support-recruitment-specialist.md +508 -0
- package/data/source.json +2 -2
- package/data/t-team.config.json +826 -1126
- package/data/teams.json +486 -672
- package/data/teams.resolved.json +743 -1109
- package/data/zh/COVERAGE.json +4 -4
- package/data/zh/company/chief-of-staff.md +278 -0
- package/data/zh/descriptions.json +21 -14
- package/data/zh/divisions.json +3 -3
- package/data/zh/engineering/engineering-security-engineer.md +303 -0
- package/data/zh/engineering/engineering-threat-detection-engineer.md +553 -0
- package/data/zh/finance/finance-financial-forecaster.md +213 -0
- package/data/zh/hr/hr-recruiter.md +232 -0
- package/data/zh/manual.json +4 -4
- package/data/zh/marketing/marketing-aeo-foundations.md +489 -219
- package/data/zh/marketing/marketing-ai-citation-strategist.md +692 -140
- package/data/zh/marketing/marketing-bilibili-strategist.md +194 -0
- package/data/zh/marketing/marketing-search-growth-orchestrator.md +372 -0
- package/data/zh/marketing/marketing-seo-specialist.md +427 -245
- package/data/zh/names.json +21 -14
- package/data/zh/specialized/recruitment-specialist.md +490 -31
- package/data/zh/specialized/specialized-french-consulting-market.md +173 -28
- package/data/zh/specialized/specialized-korean-business-navigator.md +198 -29
- package/data/zh/specialized/specialized-pricing-optimizer.md +254 -0
- package/data/zh/support/support-recruitment-specialist.md +508 -0
- package/lib/catalog.js +3 -3
- package/lib/client.js +85 -9
- package/lib/index.js +157 -12
- package/lib/remote-schemas.js +19 -0
- package/lib/remote.js +43 -4
- package/lib/teams/state.js +9 -0
- package/package.json +2 -2
- package/vendor/third-party-licenses/README.md +2 -2
- package/data/zh/marketing/marketing-bilibili-content-strategist.md +0 -40
- package/data/zh/specialized/specialized-chief-of-staff.md +0 -40
- /package/data/experts/{specialized/specialized-chief-of-staff.md → company/chief-of-staff.md} +0 -0
- /package/data/experts/engineering/{engineering-china-network-engineer.md → engineering-network-engineer-china.md} +0 -0
- /package/data/experts/marketing/{marketing-bilibili-content-strategist.md → marketing-bilibili-strategist.md} +0 -0
- /package/data/experts/marketing/{marketing-china-ecommerce-operator.md → marketing-ecommerce-operator.md} +0 -0
- /package/data/experts/marketing/{marketing-wechat-official-account.md → marketing-wechat-operator.md} +0 -0
- /package/data/experts/marketing/{marketing-xiaohongshu-specialist.md → marketing-xiaohongshu-operator.md} +0 -0
- /package/data/experts/{engineering/engineering-prompt-engineer.md → specialized/prompt-engineer.md} +0 -0
- /package/data/zh/engineering/{engineering-china-network-engineer.md → engineering-network-engineer-china.md} +0 -0
- /package/data/zh/manual-bodies/engineering/{engineering-china-network-engineer.md → engineering-network-engineer-china.md} +0 -0
- /package/data/zh/marketing/{marketing-china-ecommerce-operator.md → marketing-ecommerce-operator.md} +0 -0
- /package/data/zh/marketing/{marketing-wechat-official-account.md → marketing-wechat-operator.md} +0 -0
- /package/data/zh/marketing/{marketing-xiaohongshu-specialist.md → marketing-xiaohongshu-operator.md} +0 -0
- /package/data/zh/{engineering/engineering-prompt-engineer.md → specialized/prompt-engineer.md} +0 -0
package/lib/client.js
CHANGED
|
@@ -19925,6 +19925,14 @@ var teamSchema = external_exports.object({
|
|
|
19925
19925
|
teamId: external_exports.string(),
|
|
19926
19926
|
name: external_exports.string(),
|
|
19927
19927
|
workspace: external_exports.string(),
|
|
19928
|
+
// 2026-09-15:teamId 是团队名 slug 化的结果,**跨工作区会重名**,客户端把 workspacePath
|
|
19929
|
+
// 回传给 stop/deleteTeam 才能精确定位(strict schema 不声明就会被剥掉)。
|
|
19930
|
+
// 两个字段都 `.optional()` 是**兼容闸门**:客户端产物热重载,host 可能是旧的(不返回它们)——
|
|
19931
|
+
// 那时必需字段会让 `teamsSnapshotSchema.parse` 直接失败、列表**整片空白**。可选之后退化为
|
|
19932
|
+
// 「按 halted 判定按钮 + 按 teamId 全量遍历」的旧行为,而不是白屏。
|
|
19933
|
+
workspacePath: external_exports.string().optional(),
|
|
19934
|
+
/** 队长会话此刻是否在运行:客户端据此决定按钮叫「停止」还是「归档」。 */
|
|
19935
|
+
captainActive: external_exports.boolean().optional(),
|
|
19928
19936
|
captainSessionId: external_exports.string(),
|
|
19929
19937
|
phase: external_exports.string(),
|
|
19930
19938
|
halted: external_exports.boolean(),
|
|
@@ -19940,7 +19948,16 @@ var startResultSchema = external_exports.object({
|
|
|
19940
19948
|
var stopResultSchema = external_exports.object({
|
|
19941
19949
|
teamName: external_exports.string(),
|
|
19942
19950
|
cancelledTasks: external_exports.number(),
|
|
19943
|
-
alreadyHalted: external_exports.boolean()
|
|
19951
|
+
alreadyHalted: external_exports.boolean(),
|
|
19952
|
+
// 2026-09-15 起,「停止」在队长会话已结束(或团队早已 halted)时改为**归档**:
|
|
19953
|
+
// 这个字段让面板知道该报「已停止」还是「已归档」。
|
|
19954
|
+
archived: external_exports.boolean()
|
|
19955
|
+
});
|
|
19956
|
+
var deleteTeamResultSchema = external_exports.object({
|
|
19957
|
+
teamName: external_exports.string(),
|
|
19958
|
+
deleted: external_exports.boolean(),
|
|
19959
|
+
// 删掉的是归档里那份(而不是还挂在「运行中」的那份)
|
|
19960
|
+
wasArchived: external_exports.boolean()
|
|
19944
19961
|
});
|
|
19945
19962
|
var squadInputSchema = external_exports.object({
|
|
19946
19963
|
key: external_exports.string().min(1).max(64),
|
|
@@ -19998,7 +20015,7 @@ var scheduleWorkspaceSchema = external_exports.object({
|
|
|
19998
20015
|
|
|
19999
20016
|
// src/client.jsx
|
|
20000
20017
|
var NS = "t-team";
|
|
20001
|
-
var CLIENT_BUILD_VERSION = "0.3.
|
|
20018
|
+
var CLIENT_BUILD_VERSION = "0.3.26";
|
|
20002
20019
|
var uiPrefs = { showTaskBadge: true, showScheduleButton: true };
|
|
20003
20020
|
var uiPrefsListeners = /* @__PURE__ */ new Set();
|
|
20004
20021
|
function setUiPrefs(next) {
|
|
@@ -20191,6 +20208,16 @@ var zh = {
|
|
|
20191
20208
|
"teams.stopConfirm": "\u505C\u6B62\u56E2\u961F\u300C{name}\u300D\uFF1F\u672A\u5B8C\u6210\u7684\u4EFB\u52A1\u4F1A\u88AB\u53D6\u6D88\uFF0C\u5DF2\u4EA4\u4ED8\u7ED3\u679C\u4FDD\u7559\u3002",
|
|
20192
20209
|
"teams.stopping": "\u505C\u6B62\u4E2D\u2026",
|
|
20193
20210
|
"teams.stopped": "\u5DF2\u505C\u6B62 {name}\uFF08\u53D6\u6D88 {count} \u9879\u4EFB\u52A1\uFF09",
|
|
20211
|
+
// 已停止、或队长会话已结束的团队:按钮改为「归档」——它本来就已经不干活了,
|
|
20212
|
+
// 唯一还想要的动作是把它从「运行中」列表里清掉(任务记录进「看归档」)。
|
|
20213
|
+
"teams.archive": "\u5F52\u6863",
|
|
20214
|
+
"teams.archiveConfirm": "\u5F52\u6863\u56E2\u961F\u300C{name}\u300D\uFF1F\u5B83\u4F1A\u4ECE\u300C\u8FD0\u884C\u4E2D\u300D\u79FB\u5230\u300C\u770B\u5F52\u6863\u300D\uFF0C\u4EFB\u52A1\u8BB0\u5F55\u4FDD\u7559\u3002",
|
|
20215
|
+
"teams.archiving": "\u5F52\u6863\u4E2D\u2026",
|
|
20216
|
+
"teams.archivedNote": "\u5DF2\u5F52\u6863 {name}",
|
|
20217
|
+
"teams.remove": "\u5220\u9664",
|
|
20218
|
+
"teams.removeConfirm": "\u5220\u9664\u56E2\u961F\u300C{name}\u300D\uFF1F\u56E2\u961F\u72B6\u6001\u4E0E\u4EFB\u52A1\u8BB0\u5F55\u4F1A\u88AB\u6C38\u4E45\u5220\u9664\uFF0C\u65E0\u6CD5\u6062\u590D\u3002",
|
|
20219
|
+
"teams.removing": "\u5220\u9664\u4E2D\u2026",
|
|
20220
|
+
"teams.removed": "\u5DF2\u5220\u9664 {name}",
|
|
20194
20221
|
"teams.refresh": "\u5237\u65B0",
|
|
20195
20222
|
"teams.tasks": "{done}/{total} \u4EFB\u52A1",
|
|
20196
20223
|
"teams.members": "{count} \u540D\u6210\u5458",
|
|
@@ -20452,6 +20479,14 @@ var en = {
|
|
|
20452
20479
|
"teams.stopConfirm": 'Stop team "{name}"? Unfinished tasks are cancelled; delivered results are kept.',
|
|
20453
20480
|
"teams.stopping": "Stopping\u2026",
|
|
20454
20481
|
"teams.stopped": "Stopped {name} ({count} tasks cancelled)",
|
|
20482
|
+
"teams.archive": "Archive",
|
|
20483
|
+
"teams.archiveConfirm": 'Archive team "{name}"? It moves from active to archived; task records are kept.',
|
|
20484
|
+
"teams.archiving": "Archiving\u2026",
|
|
20485
|
+
"teams.archivedNote": "Archived {name}",
|
|
20486
|
+
"teams.remove": "Delete",
|
|
20487
|
+
"teams.removeConfirm": 'Delete team "{name}"? Its state and task records are permanently removed; this cannot be undone.',
|
|
20488
|
+
"teams.removing": "Deleting\u2026",
|
|
20489
|
+
"teams.removed": "Deleted {name}",
|
|
20455
20490
|
"teams.refresh": "Refresh",
|
|
20456
20491
|
"teams.tasks": "{done}/{total} tasks",
|
|
20457
20492
|
"teams.members": "{count} members",
|
|
@@ -20645,6 +20680,9 @@ var CSS = `
|
|
|
20645
20680
|
.t-team-chip:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
|
|
20646
20681
|
.t-team-chip:disabled{opacity:.5;cursor:default}
|
|
20647
20682
|
.t-team-chip[data-active="true"]{background:var(--dsw-alias-button-ghost-active-fill);border-color:var(--dsw-alias-button-ghost-active-border);color:var(--dsw-alias-label-primary);font-weight:600}
|
|
20683
|
+
/* \u7834\u574F\u6027\u52A8\u4F5C\uFF08\u5220\u9664\u56E2\u961F\uFF09\uFF1A\u5E38\u6001\u5C31\u5E26\u4E0A\u9519\u8BEF\u8272\uFF0C\u522B\u7B49 hover \u624D\u63D0\u793A\u3002 */
|
|
20684
|
+
.t-team-chip[data-tone="error"]{color:var(--dsw-alias-state-error-primary);border-color:color-mix(in srgb,var(--dsw-alias-state-error-primary) 40%,transparent)}
|
|
20685
|
+
.t-team-chip[data-tone="error"]:hover{background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 12%,transparent);color:var(--dsw-alias-state-error-primary)}
|
|
20648
20686
|
.t-team-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:10px 12px}
|
|
20649
20687
|
.t-team-group{margin-top:6px}
|
|
20650
20688
|
.t-team-division{font-size:12px;font-weight:600;color:var(--dsw-alias-label-secondary);margin:2px 0 6px}
|
|
@@ -20915,9 +20953,16 @@ var TYPERT_REMOTE = {
|
|
|
20915
20953
|
direct("getTeams", [
|
|
20916
20954
|
{ name: "archived", wire: "archived", source: "json", codec: { mode: "strict", typeSymbol: "boolean", schema: external_exports.boolean() } }
|
|
20917
20955
|
], "TTeamTeams", teamsSnapshotSchema),
|
|
20956
|
+
// workspacePath 标 acceptsUndefined:host 若是旧版(快照里没这个字段),这里就是 undefined,
|
|
20957
|
+
// 请求里不带该键,宿主退回全量遍历;不带标记会在 gateway 侧被判 arguments-invalid。
|
|
20918
20958
|
direct("stopTeam", [
|
|
20919
|
-
{ name: "teamId", wire: "teamId", source: "json", codec: { mode: "strict", typeSymbol: "string", schema: external_exports.string().min(1).max(128) } }
|
|
20959
|
+
{ name: "teamId", wire: "teamId", source: "json", codec: { mode: "strict", typeSymbol: "string", schema: external_exports.string().min(1).max(128) } },
|
|
20960
|
+
{ name: "workspacePath", wire: "workspacePath", source: "json", acceptsUndefined: true, codec: { mode: "strict", typeSymbol: "string", schema: external_exports.string().max(1024) } }
|
|
20920
20961
|
], "TTeamStopResult", stopResultSchema),
|
|
20962
|
+
direct("deleteTeam", [
|
|
20963
|
+
{ name: "teamId", wire: "teamId", source: "json", codec: { mode: "strict", typeSymbol: "string", schema: external_exports.string().min(1).max(128) } },
|
|
20964
|
+
{ name: "workspacePath", wire: "workspacePath", source: "json", acceptsUndefined: true, codec: { mode: "strict", typeSymbol: "string", schema: external_exports.string().max(1024) } }
|
|
20965
|
+
], "TTeamDeleteResult", deleteTeamResultSchema),
|
|
20921
20966
|
direct("createExpert", [
|
|
20922
20967
|
{ name: "expert", wire: "expert", source: "json", codec: { mode: "strict", typeSymbol: "TTeamCustomExpertInput", schema: customExpertInputSchema } }
|
|
20923
20968
|
], "TTeamCatalog", catalogSnapshotSchema),
|
|
@@ -22109,7 +22154,7 @@ function SettingsPanel({ t, ctx, remote, getActive, refreshCatalog, initialTab,
|
|
|
22109
22154
|
onClick: () => void setShowScheduleButton(!showScheduleButton)
|
|
22110
22155
|
},
|
|
22111
22156
|
/* @__PURE__ */ import_react.default.createElement("span", { className: "t-team-switch-knob" })
|
|
22112
|
-
)), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-setting-hint" }, t("settings.showScheduleButtonHint"))), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-tabs", role: "tablist" }, [["experts", "tab.experts"], ["categories", "tab.categories"], ["roster", "tab.roster"]].map(([key, label]) => /* @__PURE__ */ import_react.default.createElement(
|
|
22157
|
+
)), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-setting-hint" }, t("settings.showScheduleButtonHint"))), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-tabs", role: "tablist" }, [["experts", "tab.experts"], ["categories", "tab.categories"], ["roster", "tab.roster"], ["teams", "tab.teams"]].map(([key, label]) => /* @__PURE__ */ import_react.default.createElement(
|
|
22113
22158
|
"button",
|
|
22114
22159
|
{
|
|
22115
22160
|
className: "t-team-tab",
|
|
@@ -22121,7 +22166,7 @@ function SettingsPanel({ t, ctx, remote, getActive, refreshCatalog, initialTab,
|
|
|
22121
22166
|
onClick: () => setTab(key)
|
|
22122
22167
|
},
|
|
22123
22168
|
t(label)
|
|
22124
|
-
))), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-tabbody", hidden: tab !== "categories" }, /* @__PURE__ */ import_react.default.createElement(SectionBoundary, { t, reloadKey: tab }, /* @__PURE__ */ import_react.default.createElement(CategoriesTab, { t, remote, categories, onChanged: () => reload(true) }))), tab === "roster" && /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-tabbody" }, /* @__PURE__ */ import_react.default.createElement(SectionBoundary, { t, reloadKey: tab }, /* @__PURE__ */ import_react.default.createElement("div", null, /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-section-head" }, t("roster.squads")), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-note" }, t("roster.squadsHint"))), /* @__PURE__ */ import_react.default.createElement(SquadsTab, { t, remote, initialData: squadsPreview, initialDraft: draftPreview }), /* @__PURE__ */ import_react.default.createElement("div", null, /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-section-head" }, t("roster.teams")), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-note" }, t("roster.teamsHint"))), /* @__PURE__ */ import_react.default.createElement(TeamsTab, { t, remote, initialTeams: teamsPreview }))), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-tabbody", hidden: tab !== "experts" }, /* @__PURE__ */ import_react.default.createElement(SectionBoundary, { t, reloadKey: tab }, /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-note" }, t("roster.expertsHint")), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-bar" }, /* @__PURE__ */ import_react.default.createElement(
|
|
22169
|
+
))), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-tabbody", hidden: tab !== "categories" }, /* @__PURE__ */ import_react.default.createElement(SectionBoundary, { t, reloadKey: tab }, /* @__PURE__ */ import_react.default.createElement(CategoriesTab, { t, remote, categories, onChanged: () => reload(true) }))), tab === "roster" && /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-tabbody" }, /* @__PURE__ */ import_react.default.createElement(SectionBoundary, { t, reloadKey: tab }, /* @__PURE__ */ import_react.default.createElement("div", null, /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-section-head" }, t("roster.squads")), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-note" }, t("roster.squadsHint"))), /* @__PURE__ */ import_react.default.createElement(SquadsTab, { t, remote, initialData: squadsPreview, initialDraft: draftPreview }))), tab === "teams" && /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-tabbody" }, /* @__PURE__ */ import_react.default.createElement(SectionBoundary, { t, reloadKey: tab }, /* @__PURE__ */ import_react.default.createElement("div", null, /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-section-head" }, t("roster.teams")), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-note" }, t("roster.teamsHint"))), /* @__PURE__ */ import_react.default.createElement(TeamsTab, { t, remote, initialTeams: teamsPreview }))), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-tabbody", hidden: tab !== "experts" }, /* @__PURE__ */ import_react.default.createElement(SectionBoundary, { t, reloadKey: tab }, /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-note" }, t("roster.expertsHint")), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-bar" }, /* @__PURE__ */ import_react.default.createElement(
|
|
22125
22170
|
"input",
|
|
22126
22171
|
{
|
|
22127
22172
|
className: "t-team-input",
|
|
@@ -23028,6 +23073,7 @@ function TeamsTab({ t, remote, initialTeams, compact }) {
|
|
|
23028
23073
|
const [state, setState] = import_react.default.useState({ teams: initialTeams ?? null, error: "" });
|
|
23029
23074
|
const [archived, setArchived] = import_react.default.useState(false);
|
|
23030
23075
|
const [busyId, setBusyId] = import_react.default.useState("");
|
|
23076
|
+
const [removingId, setRemovingId] = import_react.default.useState("");
|
|
23031
23077
|
const [note, setNote] = import_react.default.useState("");
|
|
23032
23078
|
const load = import_react.default.useCallback(async (wantArchived) => {
|
|
23033
23079
|
setState((current) => ({ teams: current.teams, error: "" }));
|
|
@@ -23050,12 +23096,13 @@ function TeamsTab({ t, remote, initialTeams, compact }) {
|
|
|
23050
23096
|
}));
|
|
23051
23097
|
}
|
|
23052
23098
|
async function stop(team) {
|
|
23053
|
-
|
|
23099
|
+
const archiving = team.halted === true || team.captainActive === false;
|
|
23100
|
+
if (!window.confirm(t(archiving ? "teams.archiveConfirm" : "teams.stopConfirm", { name: team.name }))) return;
|
|
23054
23101
|
setBusyId(team.teamId);
|
|
23055
23102
|
setNote("");
|
|
23056
23103
|
try {
|
|
23057
|
-
const result = unwrap(await remote.stopTeam(team.teamId), "stopTeam");
|
|
23058
|
-
setNote(t("teams.stopped", { name: result?.teamName ?? team.name, count: result?.cancelledTasks ?? 0 }));
|
|
23104
|
+
const result = unwrap(await remote.stopTeam(team.teamId, team.workspacePath), "stopTeam");
|
|
23105
|
+
setNote(result?.archived === true ? t("teams.archivedNote", { name: result?.teamName ?? team.name }) : t("teams.stopped", { name: result?.teamName ?? team.name, count: result?.cancelledTasks ?? 0 }));
|
|
23059
23106
|
await load(archived);
|
|
23060
23107
|
} catch (cause) {
|
|
23061
23108
|
setState({ teams: state.teams, error: cause instanceof Error ? cause.message : String(cause) });
|
|
@@ -23063,14 +23110,43 @@ function TeamsTab({ t, remote, initialTeams, compact }) {
|
|
|
23063
23110
|
setBusyId("");
|
|
23064
23111
|
}
|
|
23065
23112
|
}
|
|
23113
|
+
async function remove(team) {
|
|
23114
|
+
if (!window.confirm(t("teams.removeConfirm", { name: team.name }))) return;
|
|
23115
|
+
setRemovingId(team.teamId);
|
|
23116
|
+
setNote("");
|
|
23117
|
+
try {
|
|
23118
|
+
const result = unwrap(await remote.deleteTeam(team.teamId, team.workspacePath), "deleteTeam");
|
|
23119
|
+
setNote(t("teams.removed", { name: result?.teamName ?? team.name }));
|
|
23120
|
+
await load(archived);
|
|
23121
|
+
} catch (cause) {
|
|
23122
|
+
setState({ teams: state.teams, error: cause instanceof Error ? cause.message : String(cause) });
|
|
23123
|
+
} finally {
|
|
23124
|
+
setRemovingId("");
|
|
23125
|
+
}
|
|
23126
|
+
}
|
|
23066
23127
|
const teams = state.teams;
|
|
23067
23128
|
return /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-tabbody" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-row" }, compact !== true && /* @__PURE__ */ import_react.default.createElement("button", { className: "t-team-chip", type: "button", onClick: () => setArchived((value) => !value) }, archived ? t("teams.showActive") : t("teams.showArchived")), /* @__PURE__ */ import_react.default.createElement("button", { className: "t-team-chip", type: "button", onClick: () => void load(archived) }, t("teams.refresh")), /* @__PURE__ */ import_react.default.createElement("button", { className: "t-team-chip", type: "button", onClick: () => openPanel((teams ?? [])[0]) }, t("teams.openPanel"))), note !== "" && /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-ok" }, note), state.error !== "" && /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-error" }, state.error), teams === null && /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-meta" }, t("teams.loading")), teams !== null && teams.length === 0 && /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-meta" }, archived ? t("teams.emptyArchived") : t("teams.empty")), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-teams" }, (teams ?? []).map((team) => {
|
|
23068
23129
|
const taskList = Array.isArray(team?.tasks) ? team.tasks : [];
|
|
23069
23130
|
const memberList = Array.isArray(team?.members) ? team.members : [];
|
|
23070
23131
|
const done = taskList.filter((task) => task?.status === "completed").length;
|
|
23071
23132
|
const working = memberList.filter((member) => member?.activity === "working").length;
|
|
23133
|
+
const archivable = team.halted === true || team.captainActive === false;
|
|
23072
23134
|
const phase = team.halted === true ? t("teams.halted") : archived ? t("teams.archived") : team.phase === "staged" ? t("teams.staged") : t("teams.running");
|
|
23073
|
-
return
|
|
23135
|
+
return (
|
|
23136
|
+
// key 必须带上工作区:teamId 跨工作区会重名,只用 teamId 会让两行共用同一个 key
|
|
23137
|
+
// (同列表内渲染状态互相串)。
|
|
23138
|
+
/* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-team", key: `${team.workspacePath ?? ""}:${team.teamId}` }, /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-team-head" }, /* @__PURE__ */ import_react.default.createElement("span", { className: "t-team-team-name", title: `${team.name} \xB7 ${team.teamId}` }, team.name), /* @__PURE__ */ import_react.default.createElement("span", { className: "t-team-badge", "data-tone": team.halted === true ? "error" : team.phase === "staged" ? "warn" : void 0 }, phase)), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-team-meta" }, /* @__PURE__ */ import_react.default.createElement("span", null, t("teams.members", { count: memberList.length })), /* @__PURE__ */ import_react.default.createElement("span", null, working > 0 ? `${t("teams.working", { count: working })} \xB7 ` : "", t("teams.tasks", { done, total: taskList.length })), /* @__PURE__ */ import_react.default.createElement("span", null, team.workspace)), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-row" }, memberList.slice(0, 8).map((member) => /* @__PURE__ */ import_react.default.createElement("span", { className: "t-team-member-chip", key: member.name, "data-unknown": member.activity === "unknown" }, member.name))), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-row" }, /* @__PURE__ */ import_react.default.createElement("button", { className: "t-team-chip", type: "button", onClick: () => openPanel(team) }, t("teams.openPanel")), !archived && /* @__PURE__ */ import_react.default.createElement("button", { className: "t-team-chip", type: "button", disabled: busyId === team.teamId || removingId === team.teamId, onClick: () => void stop(team) }, busyId === team.teamId ? archivable ? t("teams.archiving") : t("teams.stopping") : archivable ? t("teams.archive") : t("teams.stop")), /* @__PURE__ */ import_react.default.createElement(
|
|
23139
|
+
"button",
|
|
23140
|
+
{
|
|
23141
|
+
className: "t-team-chip",
|
|
23142
|
+
type: "button",
|
|
23143
|
+
"data-tone": "error",
|
|
23144
|
+
disabled: removingId === team.teamId || busyId === team.teamId,
|
|
23145
|
+
onClick: () => void remove(team)
|
|
23146
|
+
},
|
|
23147
|
+
removingId === team.teamId ? t("teams.removing") : t("teams.remove")
|
|
23148
|
+
)))
|
|
23149
|
+
);
|
|
23074
23150
|
})));
|
|
23075
23151
|
}
|
|
23076
23152
|
function resolveInputTarget(ctx, sessionId) {
|
package/lib/index.js
CHANGED
|
@@ -49,7 +49,14 @@ import { checkPlan } from "./plan-check.js";
|
|
|
49
49
|
// 定时事项引擎:T专家 自己的实现(JSON 文件 + croner),与 @weibaohui/dsh-tasks 零共享 ——
|
|
50
50
|
// 两个插件允许同时安装同时可用,所以存储、服务名、错误码、消息来源全都各走各的。
|
|
51
51
|
import { SCHEDULE_FILE_NAME, SCHEDULE_SERVICE, createScheduleEngine } from "./schedule.js";
|
|
52
|
-
import {
|
|
52
|
+
import {
|
|
53
|
+
archiveTeamDir,
|
|
54
|
+
findTeamByCaptain,
|
|
55
|
+
listArchivedTeamIds,
|
|
56
|
+
readArchivedTeam,
|
|
57
|
+
removeArchivedTeamDir,
|
|
58
|
+
removeTeamDir,
|
|
59
|
+
} from "./teams/state.js";
|
|
53
60
|
import { createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
54
61
|
// 内置团队引擎:随插件一起发布的品牌化产物在 lib/teams/ 下(源自第三方引擎,改名后
|
|
55
62
|
// 由本项目直接维护),这里在 T专家 内部挂载它 ——
|
|
@@ -1459,10 +1466,51 @@ export function apply(ctx, config) {
|
|
|
1459
1466
|
}))
|
|
1460
1467
|
.filter((root) => typeof root.stateRoot === "string");
|
|
1461
1468
|
};
|
|
1462
|
-
|
|
1469
|
+
/**
|
|
1470
|
+
* 团队 id 的格式闸门:这个值会被直接拼进文件路径(`<stateRoot>/<teamId>` 与
|
|
1471
|
+
* `<stateRoot>/archive/<teamId>`),所以要挡住的是**路径穿越**,而不是"必须长得像 slug"。
|
|
1472
|
+
*
|
|
1473
|
+
* 2026-09-15 二次修正(复核 N2):第一版要求 `sanitizeKey(teamId) === teamId`,结果是盘上
|
|
1474
|
+
* 任何**非 slug 目录名**的团队(历史遗留、手工建的目录,如 `My Team`)在设置页里变成
|
|
1475
|
+
* "按钮点得动、点了必失败"的僵尸行 —— 而它在加闸门之前是删得掉的。现在按"能不能安全拼进
|
|
1476
|
+
* 路径"判定:不接受分隔符、`..`、NUL,也不接受带目录成分的路径。
|
|
1477
|
+
*/
|
|
1478
|
+
const assertTeamId = (teamId) => {
|
|
1479
|
+
const bad = typeof teamId !== "string"
|
|
1480
|
+
|| teamId === "" || teamId === "." || teamId === ".."
|
|
1481
|
+
|| teamId.includes("/") || teamId.includes("\\") || teamId.includes("\0")
|
|
1482
|
+
|| basename(teamId) !== teamId;
|
|
1483
|
+
if (bad) {
|
|
1484
|
+
throw new Error(`团队 id 非法:${JSON.stringify(teamId)}(不能为空、不能含路径分隔符或 "..")。`);
|
|
1485
|
+
}
|
|
1486
|
+
};
|
|
1487
|
+
/**
|
|
1488
|
+
* 定位要操作的 stateRoot:给了 `workspacePath` 就**只用它**。
|
|
1489
|
+
*
|
|
1490
|
+
* 2026-09-15(独立审查发现的缺陷):`teamId` 是团队名 `sanitizeKey(name)` 的结果,
|
|
1491
|
+
* 不同工作区里的同名团队会拿到同一个 teamId —— 只按 teamId 找,`find` 会命中最靠前的那个
|
|
1492
|
+
* 工作区,于是「点 B 工作区那支的删除」实际删掉 A 工作区的目录(停止同理)。
|
|
1493
|
+
*
|
|
1494
|
+
* 空串与 undefined 都**放行**(退回全量遍历):那是「客户端比 host 旧、快照里没带
|
|
1495
|
+
* workspacePath」的降级路径 —— 此时除全遍历外无从定位,而它带着 F1 那个固有歧义。
|
|
1496
|
+
* 这是旧版本组合下唯一可能的降级,不是新客户端会走的路径。
|
|
1497
|
+
*/
|
|
1498
|
+
const locateRoots = (roots, workspacePath) => {
|
|
1499
|
+
if (typeof workspacePath !== "string" || workspacePath === "") return roots;
|
|
1500
|
+
const hit = roots.find((root) => root.path === workspacePath);
|
|
1501
|
+
if (hit === undefined) {
|
|
1502
|
+
throw new Error(`工作区不在当前列表里:${workspacePath}(它可能已被移除,或不在本机工作区注册表里)。`);
|
|
1503
|
+
}
|
|
1504
|
+
return [hit];
|
|
1505
|
+
};
|
|
1506
|
+
const compactTeam = (team, workspacePath, captainActive) => ({
|
|
1463
1507
|
teamId: team.teamId,
|
|
1464
1508
|
name: team.name,
|
|
1465
1509
|
workspace: team.workspace,
|
|
1510
|
+
/** 所属工作区路径:同名团队跨工作区时,客户端靠它精确定位(见 locateRoots)。 */
|
|
1511
|
+
workspacePath,
|
|
1512
|
+
/** 队长会话此刻是否在运行:客户端据此决定按钮叫「停止」还是「归档」,与服务端判定对齐。 */
|
|
1513
|
+
captainActive,
|
|
1466
1514
|
// 面板/设置页的「打开活动面板」要带 captainSessionId:活动面板是会话作用域的,
|
|
1467
1515
|
// 缺它就只能匹配当前会话的团队,别的会话(或历史归档)点按钮会静默无反应。
|
|
1468
1516
|
captainSessionId: team.captainSessionId,
|
|
@@ -1487,10 +1535,33 @@ export function apply(ctx, config) {
|
|
|
1487
1535
|
async list(archived = false) {
|
|
1488
1536
|
const roots = teamRoots();
|
|
1489
1537
|
if (roots.length === 0) return [];
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1538
|
+
// 逐工作区收集(而不是一次性把 roots 交给 snapshot 层):只有这样才能把**每支团队**
|
|
1539
|
+
// 与它所属的工作区路径、以及「队长此刻是否在运行」一起带出去(见 compactTeam 注释)。
|
|
1540
|
+
const agents = ctx.get("agents");
|
|
1541
|
+
const out = [];
|
|
1542
|
+
const failures = [];
|
|
1543
|
+
for (const root of roots) {
|
|
1544
|
+
let snapshots;
|
|
1545
|
+
try {
|
|
1546
|
+
snapshots = archived
|
|
1547
|
+
? await collectArchivedTeamsActivity(ctx, [root])
|
|
1548
|
+
: await collectTeamsActivity(ctx, [root]);
|
|
1549
|
+
} catch (error) {
|
|
1550
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
1551
|
+
failures.push(`${root.stateRoot}:${reason}`);
|
|
1552
|
+
ctx.logger?.warn?.(`[t-team] 读取团队活动失败:${root.stateRoot}(${reason})`);
|
|
1553
|
+
continue;
|
|
1554
|
+
}
|
|
1555
|
+
for (const snapshot of snapshots) {
|
|
1556
|
+
out.push(compactTeam(snapshot, root.path, agents?.get?.(snapshot.captainSessionId) !== undefined));
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
// 全部工作区都读失败时不要返回空数组 —— UI 会把它当「没有团队」显示,
|
|
1560
|
+
// 真实原因(EACCES 之类)就此消失(与 stop/remove 里的 N-4 同一条原则)。
|
|
1561
|
+
if (out.length === 0 && failures.length > 0 && failures.length === roots.length) {
|
|
1562
|
+
throw new Error(`读取团队活动失败:${roots.length} 个工作区全部读取失败(${failures.join(";")})。`);
|
|
1563
|
+
}
|
|
1564
|
+
return out;
|
|
1494
1565
|
},
|
|
1495
1566
|
/** 面板里点一支小队:投一条 `/t --profile <key>` 让队长按该小队接活(目标由队长追问用户)。 */
|
|
1496
1567
|
async start(profileKey, sessionId) {
|
|
@@ -1512,8 +1583,9 @@ export function apply(ctx, config) {
|
|
|
1512
1583
|
members: hit.members.length,
|
|
1513
1584
|
};
|
|
1514
1585
|
},
|
|
1515
|
-
async stop(teamId) {
|
|
1516
|
-
|
|
1586
|
+
async stop(teamId, workspacePath) {
|
|
1587
|
+
assertTeamId(teamId);
|
|
1588
|
+
const roots = locateRoots(teamRoots(), workspacePath);
|
|
1517
1589
|
// 逐 root 读取失败时**留住根因**:过去 `catch { continue; }` 把 EACCES 之类换成
|
|
1518
1590
|
// 下面那句「没找到团队 …(它可能已被归档)」,用户按提示去查归档永远查不到(N-4)。
|
|
1519
1591
|
const failures = [];
|
|
@@ -1529,18 +1601,91 @@ export function apply(ctx, config) {
|
|
|
1529
1601
|
}
|
|
1530
1602
|
const team = snapshots.find((item) => item.teamId === teamId);
|
|
1531
1603
|
if (team === undefined) continue;
|
|
1604
|
+
// 2026-09-15 修(用户报「运行中的团队删不掉」):过去只要队长会话不在运行就直接抛错,
|
|
1605
|
+
// 于是 DSH 重启后残留下来的团队在设置页里**永远**清不掉 —— 它躺在「运行中」列表里,
|
|
1606
|
+
// 点一次报一次「队长会话已不在运行中」,而那句提示让用户去活动面板归档,活动面板是
|
|
1607
|
+
// 会话作用域的、队长会话都没了自然也打不开。现在按状态分流:
|
|
1608
|
+
// · 队长在运行且还没停 → haltTeamWork(原行为:取消未完成任务 + 中断成员)
|
|
1609
|
+
// · 队长不在运行、或团队早已 halted → **归档**(目录移进 <stateRoot>/archive/,
|
|
1610
|
+
// 从「运行中」列表消失,「看归档」里仍能查到它的任务记录)
|
|
1532
1611
|
const captain = ctx.get("agents")?.get?.(team.captainSessionId);
|
|
1533
|
-
if (captain
|
|
1534
|
-
|
|
1612
|
+
if (captain !== undefined && team.halted !== true) {
|
|
1613
|
+
const result = await haltTeamWork({ ctx, stateRoot: root.stateRoot, teamId, captain });
|
|
1614
|
+
return {
|
|
1615
|
+
teamName: result.teamName,
|
|
1616
|
+
cancelledTasks: result.cancelledTasks,
|
|
1617
|
+
alreadyHalted: result.alreadyHalted === true,
|
|
1618
|
+
archived: false,
|
|
1619
|
+
};
|
|
1535
1620
|
}
|
|
1536
|
-
|
|
1537
|
-
|
|
1621
|
+
await archiveTeamDir(root.stateRoot, teamId);
|
|
1622
|
+
ctx.logger?.info?.(`[t-team] 团队「${team.name}」已归档(队长不在运行:${String(captain === undefined)})。`);
|
|
1623
|
+
return {
|
|
1624
|
+
teamName: team.name,
|
|
1625
|
+
cancelledTasks: 0,
|
|
1626
|
+
alreadyHalted: team.halted === true,
|
|
1627
|
+
archived: true,
|
|
1628
|
+
};
|
|
1538
1629
|
}
|
|
1539
1630
|
// 读不到任何 root 时不要用「可能已归档」这种猜测掩盖根因:把真实失败原因一并报出来。
|
|
1540
1631
|
throw new Error(failures.length > 0
|
|
1541
1632
|
? `没找到团队 ${teamId}:${roots.length} 个工作区里${failures.length} 个读取失败(${failures.join(";")})。`
|
|
1542
1633
|
: `没找到团队 ${teamId} 的状态目录(它可能已被归档)。`);
|
|
1543
1634
|
},
|
|
1635
|
+
/**
|
|
1636
|
+
* 彻底删除一支团队(运行中的与已归档的都收)—— 设置页那一行上的「删除」按钮。
|
|
1637
|
+
*
|
|
1638
|
+
* 2026-09-15 加:用户报「已经停止的团队没有删除按钮」。stop 只能让它停下/进归档,
|
|
1639
|
+
* 目录与任务记录都还在;要真正清掉就得删目录。运行中的先照常停一次(取消未完成任务、
|
|
1640
|
+
* 中断成员),避免留下还在跑的成员;队长不在运行就直接删(成员随会话结束已经没了)。
|
|
1641
|
+
*/
|
|
1642
|
+
async remove(teamId, workspacePath) {
|
|
1643
|
+
assertTeamId(teamId);
|
|
1644
|
+
const roots = locateRoots(teamRoots(), workspacePath);
|
|
1645
|
+
const failures = [];
|
|
1646
|
+
for (const root of roots) {
|
|
1647
|
+
let snapshots;
|
|
1648
|
+
try {
|
|
1649
|
+
snapshots = await collectTeamsActivity(ctx, [root]);
|
|
1650
|
+
} catch (error) {
|
|
1651
|
+
const reason = error instanceof Error ? `${error.message}` : String(error);
|
|
1652
|
+
failures.push(`${root.stateRoot}:${reason}`);
|
|
1653
|
+
ctx.logger?.warn?.(`[t-team] 读取团队活动失败:${root.stateRoot}(${reason})`);
|
|
1654
|
+
continue;
|
|
1655
|
+
}
|
|
1656
|
+
const team = snapshots.find((item) => item.teamId === teamId);
|
|
1657
|
+
if (team !== undefined) {
|
|
1658
|
+
const captain = ctx.get("agents")?.get?.(team.captainSessionId);
|
|
1659
|
+
if (captain !== undefined && team.halted !== true) {
|
|
1660
|
+
try {
|
|
1661
|
+
await haltTeamWork({ ctx, stateRoot: root.stateRoot, teamId, captain });
|
|
1662
|
+
} catch (error) {
|
|
1663
|
+
// 停不下来不该挡住删除:目录都要删了,成员随会话结束时也会被回收。
|
|
1664
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
1665
|
+
ctx.logger?.warn?.(`[t-team] 删除前停止团队「${team.name}」失败(继续删除):${reason}`);
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
await removeTeamDir(root.stateRoot, teamId);
|
|
1669
|
+
// 防护(独立审查 F3):队长/成员若还有在途写盘,`writeTeam` 会 `mkdir -p` 把目录
|
|
1670
|
+
// **重建**出来(state.js 里那行是无条件的),于是「删掉的团队」过一会儿又冒出来。
|
|
1671
|
+
// 这里确认一次:真被重建就再删一次并出声 —— 静默留下空壳比报错更难查。
|
|
1672
|
+
if (existsSync(join(root.stateRoot, teamId))) {
|
|
1673
|
+
ctx.logger?.warn?.(`[t-team] 团队「${team.name}」删除后有写入者重建了目录,已再次删除(可能有成员在收尾)。`);
|
|
1674
|
+
await removeTeamDir(root.stateRoot, teamId);
|
|
1675
|
+
}
|
|
1676
|
+
return { teamName: team.name, deleted: true, wasArchived: false };
|
|
1677
|
+
}
|
|
1678
|
+
const archivedIds = await listArchivedTeamIds(root.stateRoot);
|
|
1679
|
+
if (archivedIds.includes(teamId)) {
|
|
1680
|
+
const state = await readArchivedTeam(root.stateRoot, teamId);
|
|
1681
|
+
await removeArchivedTeamDir(root.stateRoot, teamId);
|
|
1682
|
+
return { teamName: state?.name ?? teamId, deleted: true, wasArchived: true };
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
throw new Error(failures.length > 0
|
|
1686
|
+
? `没找到团队 ${teamId}:${roots.length} 个工作区里${failures.length} 个读取失败(${failures.join(";")})。`
|
|
1687
|
+
: `没找到团队 ${teamId} 的状态目录(它可能已被删除)。`);
|
|
1688
|
+
},
|
|
1544
1689
|
});
|
|
1545
1690
|
|
|
1546
1691
|
// ---- `/t` 小队短命令(把 /t <小队> <目标> 转成引擎认得的 /t --profile <key> <目标> 用户消息)----
|
package/lib/remote-schemas.js
CHANGED
|
@@ -141,6 +141,14 @@ export const teamSchema = z.object({
|
|
|
141
141
|
teamId: z.string(),
|
|
142
142
|
name: z.string(),
|
|
143
143
|
workspace: z.string(),
|
|
144
|
+
// 2026-09-15:teamId 是团队名 slug 化的结果,**跨工作区会重名**,客户端把 workspacePath
|
|
145
|
+
// 回传给 stop/deleteTeam 才能精确定位(strict schema 不声明就会被剥掉)。
|
|
146
|
+
// 两个字段都 `.optional()` 是**兼容闸门**:客户端产物热重载,host 可能是旧的(不返回它们)——
|
|
147
|
+
// 那时必需字段会让 `teamsSnapshotSchema.parse` 直接失败、列表**整片空白**。可选之后退化为
|
|
148
|
+
// 「按 halted 判定按钮 + 按 teamId 全量遍历」的旧行为,而不是白屏。
|
|
149
|
+
workspacePath: z.string().optional(),
|
|
150
|
+
/** 队长会话此刻是否在运行:客户端据此决定按钮叫「停止」还是「归档」。 */
|
|
151
|
+
captainActive: z.boolean().optional(),
|
|
144
152
|
captainSessionId: z.string(),
|
|
145
153
|
phase: z.string(),
|
|
146
154
|
halted: z.boolean(),
|
|
@@ -160,6 +168,17 @@ export const stopResultSchema = z.object({
|
|
|
160
168
|
teamName: z.string(),
|
|
161
169
|
cancelledTasks: z.number(),
|
|
162
170
|
alreadyHalted: z.boolean(),
|
|
171
|
+
// 2026-09-15 起,「停止」在队长会话已结束(或团队早已 halted)时改为**归档**:
|
|
172
|
+
// 这个字段让面板知道该报「已停止」还是「已归档」。
|
|
173
|
+
archived: z.boolean(),
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
/** 「删除团队」的结果:与「停止/归档」不同,这是不可逆的清理。 */
|
|
177
|
+
export const deleteTeamResultSchema = z.object({
|
|
178
|
+
teamName: z.string(),
|
|
179
|
+
deleted: z.boolean(),
|
|
180
|
+
// 删掉的是归档里那份(而不是还挂在「运行中」的那份)
|
|
181
|
+
wasArchived: z.boolean(),
|
|
163
182
|
});
|
|
164
183
|
|
|
165
184
|
/** 面板提交的小队定义(saveSquads 的入参)。 */
|
package/lib/remote.js
CHANGED
|
@@ -40,6 +40,7 @@ import {
|
|
|
40
40
|
squadsSnapshotSchema,
|
|
41
41
|
startResultSchema,
|
|
42
42
|
stopResultSchema,
|
|
43
|
+
deleteTeamResultSchema,
|
|
43
44
|
teamMemberSchema,
|
|
44
45
|
teamSchema,
|
|
45
46
|
teamsSnapshotSchema,
|
|
@@ -195,6 +196,12 @@ const DESCRIPTORS = [
|
|
|
195
196
|
codec: { mode: "strict", typeSymbol: "boolean", schema: z.boolean() },
|
|
196
197
|
},
|
|
197
198
|
], "TTeamTeams", teamsSnapshotSchema),
|
|
199
|
+
// teamId 是**团队名 slug 化**的产物,跨工作区会重名;workspacePath 让宿主精确定位到
|
|
200
|
+
// 「用户点的那一行」所在的工作区。
|
|
201
|
+
// `acceptsUndefined: true` 是**兼容闸门**:DSH 的客户端产物是热重载的,host 更新后浏览器
|
|
202
|
+
// 可能还挂着上一版 client bundle —— 那时它只会传 teamId。没有这个标记,gateway 会直接
|
|
203
|
+
// 判 `arguments-invalid: missing "workspacePath"`,用户点「停止/删除」立刻炸;
|
|
204
|
+
// 标上之后旧客户端照旧能调,宿主退回「全量遍历」的旧行为。
|
|
198
205
|
descriptor("stopTeam", [
|
|
199
206
|
{
|
|
200
207
|
name: "teamId",
|
|
@@ -202,7 +209,30 @@ const DESCRIPTORS = [
|
|
|
202
209
|
source: "json",
|
|
203
210
|
codec: { mode: "strict", typeSymbol: "string", schema: z.string().min(1).max(128) },
|
|
204
211
|
},
|
|
212
|
+
{
|
|
213
|
+
name: "workspacePath",
|
|
214
|
+
wire: "workspacePath",
|
|
215
|
+
source: "json",
|
|
216
|
+
acceptsUndefined: true,
|
|
217
|
+
codec: { mode: "strict", typeSymbol: "string", schema: z.string().max(1024) },
|
|
218
|
+
},
|
|
205
219
|
], "TTeamStopResult", stopResultSchema),
|
|
220
|
+
// 彻底删除一支团队(运行中的与已归档的都收):设置页「运行中的团队」那一行的「删除」按钮。
|
|
221
|
+
descriptor("deleteTeam", [
|
|
222
|
+
{
|
|
223
|
+
name: "teamId",
|
|
224
|
+
wire: "teamId",
|
|
225
|
+
source: "json",
|
|
226
|
+
codec: { mode: "strict", typeSymbol: "string", schema: z.string().min(1).max(128) },
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
name: "workspacePath",
|
|
230
|
+
wire: "workspacePath",
|
|
231
|
+
source: "json",
|
|
232
|
+
acceptsUndefined: true,
|
|
233
|
+
codec: { mode: "strict", typeSymbol: "string", schema: z.string().max(1024) },
|
|
234
|
+
},
|
|
235
|
+
], "TTeamDeleteResult", deleteTeamResultSchema),
|
|
206
236
|
// 自建专家的增删改:都回传刷新后的整份名册快照,面板拿到就能直接重绘。
|
|
207
237
|
descriptor("createExpert", [
|
|
208
238
|
{
|
|
@@ -562,15 +592,24 @@ class TTeamRemote extends TypertRemoteService {
|
|
|
562
592
|
}
|
|
563
593
|
}
|
|
564
594
|
|
|
565
|
-
/**
|
|
566
|
-
async stopTeam(teamId) {
|
|
595
|
+
/** 停止一个团队(等价于活动面板里的停止)。`workspacePath` 用于同名团队的精确定位。 */
|
|
596
|
+
async stopTeam(teamId, workspacePath = "") {
|
|
567
597
|
try {
|
|
568
|
-
return await this.teams().stop(teamId);
|
|
598
|
+
return await this.teams().stop(teamId, workspacePath);
|
|
569
599
|
} catch (error) {
|
|
570
600
|
throw this.businessError(error, "tTeam/stop-failed");
|
|
571
601
|
}
|
|
572
602
|
}
|
|
573
603
|
|
|
604
|
+
/** 彻底删除一个团队(运行中的先停再删;已归档的直接删)。不可逆。 */
|
|
605
|
+
async deleteTeam(teamId, workspacePath = "") {
|
|
606
|
+
try {
|
|
607
|
+
return await this.teams().remove(teamId, workspacePath);
|
|
608
|
+
} catch (error) {
|
|
609
|
+
throw this.businessError(error, "tTeam/delete-failed");
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
574
613
|
/**
|
|
575
614
|
* 业务错误统一转 RemoteError(裸 Error 过线会退化成 gateway/internal 并丢掉 code)。
|
|
576
615
|
*
|
|
@@ -658,7 +697,7 @@ class TTeamRemote extends TypertRemoteService {
|
|
|
658
697
|
}
|
|
659
698
|
|
|
660
699
|
exposeRemoteMethods(TTeamRemote, [
|
|
661
|
-
"getCatalog", "setEnabled", "getPrompt", "getSquads", "saveSquads", "getTeams", "stopTeam", "startSquad",
|
|
700
|
+
"getCatalog", "setEnabled", "getPrompt", "getSquads", "saveSquads", "getTeams", "stopTeam", "deleteTeam", "startSquad",
|
|
662
701
|
"getUiPrefs", "setUiPrefs",
|
|
663
702
|
"getSchedule", "createScheduleItem", "updateScheduleItem", "deleteScheduleItem", "runScheduleItem", "previewSchedule", "ensureScheduleWorkspace",
|
|
664
703
|
"createExpert", "updateExpert", "deleteExpert",
|
package/lib/teams/state.js
CHANGED
|
@@ -855,6 +855,15 @@ export async function archiveTeamDir(stateRoot, teamId) {
|
|
|
855
855
|
if (displaced)
|
|
856
856
|
await rm(previous, { recursive: true, force: true }).catch(() => undefined);
|
|
857
857
|
}
|
|
858
|
+
/**
|
|
859
|
+
* Remove an archived team's directory — the counterpart of {@link archiveTeamDir}
|
|
860
|
+
* for the "delete it for good" action. Irreversible: the task record goes with it.
|
|
861
|
+
* @param stateRoot - resolved absolute state root directory.
|
|
862
|
+
* @param teamId - the team id.
|
|
863
|
+
*/
|
|
864
|
+
export async function removeArchivedTeamDir(stateRoot, teamId) {
|
|
865
|
+
await rm(join(stateRoot, 'archive', teamId), { recursive: true, force: true });
|
|
866
|
+
}
|
|
858
867
|
/**
|
|
859
868
|
* Read one archived team (already moved under `archive/`), or undefined when
|
|
860
869
|
* it was never archived.
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-t-expert",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.26",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "T Expert — a
|
|
5
|
+
"description": "T Expert — a 323-expert, 22-division roster with full Chinese translations, as a standalone DeepSeek Harness plugin, with a built-in multi-agent team engine (T Team).",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "jiuaiwo",
|
|
8
8
|
"keywords": [
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
|
|
6
6
|
| 文件 | 覆盖的随包内容 | 来源 |
|
|
7
7
|
| --- | --- | --- |
|
|
8
|
-
| `agency-agents.LICENSE` | `data/experts/`(22 分区 /
|
|
9
|
-
| `agency-agents-zh.LICENSE` | `data/zh/`
|
|
8
|
+
| `agency-agents.LICENSE` | `data/experts/`(22 分区 / 323 位;其中 321 位来自第三方 —— 279 位为上游仓库逐字节镜像、35 位来自名册来源包快照、7 位于 2026-09-15 与 `agency-agents-zh` 源仓库对齐时补齐;余 2 位为本仓自建,不由本许可覆盖) | The Agency / AgentLand 名册快照,MIT |
|
|
9
|
+
| `agency-agents-zh.LICENSE` | `data/zh/` 中的中文名字、简介与人格正文(含 2026-09-15 对齐时直接取自该仓库的 7 篇正文) | `agency-agents-zh` 中文翻译与本地化资产,MIT |
|
|
10
10
|
|
|
11
11
|
两份文本都是从实际用于生成快照的来源包内**逐字节复制**的,未经改写:
|
|
12
12
|
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: "B站内容运营专家"
|
|
3
|
-
description: "运营 B 站账号内容,策划选题、对接 UP 主合作,按平台算法优化视频与弹幕互动,提升播放量和粉丝增长。"
|
|
4
|
-
emoji: "🎬"
|
|
5
|
-
color: "pink"
|
|
6
|
-
vibe: "Speaks fluent danmaku and grows your brand on B站."
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
# B站内容运营专家
|
|
10
|
-
|
|
11
|
-
你是**B站内容运营专家**。运营 B 站账号内容,策划选题、对接 UP 主合作,按平台算法优化视频与弹幕互动,提升播放量和粉丝增长。
|
|
12
|
-
|
|
13
|
-
## 核心使命
|
|
14
|
-
|
|
15
|
-
把用户目标转化为本领域中可执行、可验证、可交付的结果。在给出方案时同时关注正确性、现实约束、风险和后续维护,不用空泛建议代替专业判断。
|
|
16
|
-
|
|
17
|
-
## 工作原则
|
|
18
|
-
|
|
19
|
-
- 先明确目标、使用对象、约束条件、已有证据和验收标准,再提出建议。
|
|
20
|
-
- 严格区分已知事实、合理假设、估算结果和待确认事项,不编造数据、来源、系统行为或合规结论。
|
|
21
|
-
- 使用本领域通行的方法、标准和术语;对关键取舍说明依据,不以短期便利掩盖安全、法律、财务、运营或质量风险。
|
|
22
|
-
- 优先交付具体产物,例如方案、清单、决策表、规格、计算过程、审查结论或实施步骤,而不是泛泛讲解。
|
|
23
|
-
- 尊重用户给出的真实边界。只有缺失信息会实质改变结论时才提出问题;否则明确假设后继续推进。
|
|
24
|
-
- 最小化处理敏感信息,不泄露凭证、个人信息和机密业务数据。
|
|
25
|
-
|
|
26
|
-
## 执行流程
|
|
27
|
-
|
|
28
|
-
1. **界定任务**:确认期望结果,以及当前需要做出的决策或交付物。
|
|
29
|
-
2. **核对材料**:检查用户提供的信息和术语,识别缺失、冲突及可信度问题。
|
|
30
|
-
3. **专业分析**:运用领域方法推导结论,能量化时给出计算,并检查边界情况和失败模式。
|
|
31
|
-
4. **形成交付**:输出可直接执行的结果;需要时注明负责人、依赖、验收标准和下一步。
|
|
32
|
-
5. **自我校验**:检查内容是否前后一致、现实可行、符合约束,并确认已经正面回答用户问题。
|
|
33
|
-
|
|
34
|
-
## 输出要求
|
|
35
|
-
|
|
36
|
-
- 先给结论或建议动作,再提供必要依据。
|
|
37
|
-
- 使用准确的专业术语,对少见概念做简短解释。
|
|
38
|
-
- 展示关键假设、计算、证据和取舍。
|
|
39
|
-
- 明确标注不确定性,以及必须由有资质专业人士复核的事项。
|
|
40
|
-
- 不堆砌通用背景,不声称完成实际未执行的工作。
|