@stofone/dsh-agent-roster 1.0.0 → 1.2.0

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 CHANGED
@@ -4,6 +4,42 @@
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [1.2.0] - 2026-11-03
8
+
9
+ ### Create a team from the row you are looking at
10
+
11
+ Creating a team used to require expanding the roster, finding the captain panel and pressing *apply*. The
12
+ one action the product is named after was the hardest thing to find, and a roster without a `captain` block
13
+ offered no path at all.
14
+
15
+ Every roster row now carries **Create team**. It opens a small dialog: team name (defaults to the roster id),
16
+ a choice between a new session and an existing one — new is the default, because quick creation must not
17
+ hijack the conversation you are in — and a one-line summary of what will be built (`N members · captain model`).
18
+ Submitting queues the same build prompt as *apply*; the toast names the session it landed in.
19
+
20
+ The boundary is unchanged, and the dialog says so on its face: the plugin never writes team state. It
21
+ prepares a session (selects the captain model when the archive declares one, queues the prompt), and that
22
+ session calls `agent_teams_create` / `agent_teams_add_member` / `roster_verify`. A roster without a captain
23
+ block can still create a team; it just will not switch the model, and the dialog says that instead of failing
24
+ silently.
25
+
26
+ ## [1.1.0] - 2026-11-03
27
+
28
+ ### The library reads as spaces, not groups
29
+
30
+ 1.0.0 made the space a real level in the format. The panel still showed collections as small labels, so the
31
+ level existed in the files and not on screen. Each group header is now the space itself: its id sits in the
32
+ same column as the roster ids (the register keeps one spine), the right side states two facts you can check
33
+ on the spot — how many rosters it holds, how many are already here — and one button pulls the whole space.
34
+
35
+ Rows also stopped lying about state. A roster that is already on this machine says so, and its action
36
+ becomes **Open** instead of **Pull**: the panel no longer offers to fetch something you already have. The
37
+ local group drops the per-row "local" label, because the group header already says it.
38
+
39
+ On a narrow panel the space header folds into two lines (id + action, then title + facts) instead of
40
+ squeezing a long space name into a vertical stack one character wide.
41
+
42
+
7
43
  ## [1.0.0] - 2026-11-03
8
44
 
9
45
  ### A space is the thing a repository holds
@@ -668,6 +704,7 @@ point is now one search field above the tabs.
668
704
  [Unreleased]: https://github.com/dsh-agent-roster/dsh-agent-roster/compare/v0.12.0...main
669
705
  [0.13.1]: https://github.com/dsh-agent-roster/dsh-agent-roster/compare/v0.13.0...v0.13.1
670
706
  [1.0.0]: https://github.com/dsh-agent-roster/dsh-agent-roster/compare/v0.13.1...v1.0.0
707
+ [1.1.0]: https://github.com/dsh-agent-roster/dsh-agent-roster/compare/v1.0.0...v1.1.0
671
708
  [0.13.0]: https://github.com/dsh-agent-roster/dsh-agent-roster/compare/v0.12.0...v0.13.0
672
709
  [0.12.0]: https://github.com/dsh-agent-roster/dsh-agent-roster/compare/v0.11.1...v0.12.0
673
710
  [0.11.1]: https://github.com/dsh-agent-roster/dsh-agent-roster/compare/v0.11.0...v0.11.1
package/README.md CHANGED
@@ -326,6 +326,11 @@ response, the tag strip summarises every tag in view with its count (multiple ta
326
326
  segmented control switches between **by source** (this machine, then one group per collection) and **flat**
327
327
  (one list, source named per row). Tag filtering is view state only; it never writes back to a file.
328
328
 
329
+ By source, each group's head **is the space**: its id sits in the same column as the roster ids, the
330
+ right side states two facts you can check on the spot — how many rosters it holds, how many are already
331
+ here — and carries one button that pulls the whole space. A roster that is already here says so in its row
332
+ and its button reads **Open**: the panel never asks you to pull something you already have.
333
+
329
334
  Multiple words are AND, not OR: `data kubernetes` returns nothing rather than every roster that merely
330
335
  mentions "data". Ranking is a pure function in `lib/search.js`, so it is unit-tested rather than eyeballed.
331
336
 
@@ -378,6 +383,12 @@ The **apply** action is the one write the panel offers here: it switches the cho
378
383
  declared model and queues a prompt that calls `agent_teams_create`, `agent_teams_add_member` per
379
384
  member and finally `roster_verify`. Creating the team stays with the runtime.
380
385
 
386
+ Since 1.2.0 the roster row itself carries **Create team**, so this no longer hides behind an
387
+ expanded card. It defaults to a *new* session — quick creation must not hijack the conversation
388
+ you are in — lets you rename the team, switches the captain model when the archive declares one,
389
+ and queues the same build prompt. The plugin still writes no team state: the row action prepares a
390
+ session, the session does the creating.
391
+
381
392
  ## Sidebar panel
382
393
 
383
394
  The roster list is a **ledger, not a wall of cards**: one row per roster — a 3px state edge (lit only when something is wrong, quiet when it is fine), a monospace id column, the title with a one-line description, right-aligned counts (members / errors / warnings / deployment), and a quiet pair of actions (edit, expand). Duplicate, copy-create and delete moved into the expanded area, which turns 105 buttons across 21 rosters into 42. Below 860px the tag pool becomes a top row and the metadata wraps. Expansions never build their own scroll box: the panel root is the only scroller, so an expanded section grows as tall as its content instead of showing "just a few rows".
@@ -519,7 +530,7 @@ test/ unit tests
519
530
 
520
531
  Verified on this machine:
521
532
 
522
- - `npm test` — 313/313 passing (format, parse, validate, registry, paths, bridge, verify buckets, digests and provenance, roster diff, capability requirements and the capability gate, library organization, registry schema, lossless JSON, write plan/apply, all routes, panel snapshot and hints, dictionary consistency, tool contracts, client loader contract, library snapshot, config view, package manifest, declared fields and captain block, schema/constant alignment, the v2 role/roster/compiled layers and their schema alignment, v1→v2 migration, run records feeding the plan, remote registry and one-click install).
533
+ - `npm test` — 315/315 passing (format, parse, validate, registry, paths, bridge, verify buckets, digests and provenance, roster diff, capability requirements and the capability gate, library organization, registry schema, lossless JSON, write plan/apply, all routes, panel snapshot and hints, dictionary consistency, tool contracts, client loader contract, library snapshot, config view, package manifest, declared fields and captain block, schema/constant alignment, the v2 role/roster/compiled layers and their schema alignment, v1→v2 migration, run records feeding the plan, remote registry and one-click install).
523
534
  - `npm run smoke` — `apply()` registers all six tools through a real `defineTool` from a DSH bundle; `roster_list` / `roster_show` / `roster_create` / `roster_verify` / `roster_diff` execute, every output survives `lossless` checks, `path` overrides are honored, effect disposal runs cleanly. The same command then loads `client.js` against a stubbed module table and asserts the three slot registrations, style injection and effect disposal.
524
535
  - CLI: `validate --dir examples` returns exit 0 over both example libraries.
525
536
  - Installed into a real profile with the plugin manager (`plugin_manager install_bundle` against this directory → `link:` dependency + bundle entry); the profile re-reads the linked files on restart.
package/README.zh.md CHANGED
@@ -323,6 +323,10 @@ node bin/cli.mjs compile --roster examples-orchestration/rosters/core-review \
323
323
  多选是「且」;右侧可以在**按来源**(本机 + 每个 collection 各一组)与**平铺**(一份清单,来源写在行内)
324
324
  之间切换。标签筛选只影响视图,不写回文件。
325
325
 
326
+ 按来源时,每一组的头就是**空间**本身:等宽的空间 id 落在与档案 id 同一列,右侧写两件当场能核对的事实
327
+ ——「几份 · 已装几份」——并带一个「拉取整个空间」按钮。已经在本机的那份,行内写「已在本机」,
328
+ 按钮变成「打开」:面板不会让你去拉一份自己已经有的档案。窄屏上这个头折成两行,空间名不会被挤成竖排。
329
+
326
330
  多个词是「且」不是「或」:`data kubernetes` 一条都不返,而不是把所有提到 data 的档案都堆上来。
327
331
  排序是 `lib/search.js` 里的纯函数,所以它被单测钉住,而不是靠肉眼看着顺。
328
332
 
@@ -366,6 +370,14 @@ node bin/cli.mjs compile --roster examples-orchestration/rosters/core-review \
366
370
  设置页显示每个 profile 里实际部署的 provider / model,每份带 captain 段的档案按字段报
367
371
  「一致 / 不一致 / 未声明 / 无法核对」。读不到部署配置时是「无法核对」,永远不会写成“通过”。
368
372
 
373
+ 面板在这里唯一会写的一步是**应用**:把选中的会话切到声明的模型,再排入一条提示词,
374
+ 让它依次调用 `agent_teams_create`、逐成员 `agent_teams_add_member`,最后 `roster_verify`。
375
+ 建团队本身仍由运行时完成。
376
+
377
+ 1.2.0 起,档案行上直接有**创建团队**,不必先展开卡片:默认开**新会话**(快速建团不该劫持
378
+ 你正在聊的这个),可改团队名,档案声明了队长模型就一并切换,然后排入同样的建团提示词。
379
+ 插件仍然不写团队状态——行上的按钮只负责把一个会话准备好,动手创建的是那个会话。
380
+
369
381
  **应用**是这块唯一会写入的动作:把选中会话切到声明里的模型,并排入一段提示词,让它依次调用
370
382
  `agent_teams_create`、逐成员 `agent_teams_add_member`,最后 `roster_verify` 核对。
371
383
  团队仍由运行时创建,插件不代劳。
@@ -501,7 +513,7 @@ test/ 单测
501
513
 
502
514
  本机已验证:
503
515
 
504
- - `npm test` —— 313/313 通过(格式、解析、校验、扫描、路径、桥接、核对四桶、摘要与溯源、档案差异、能力要求与能力闸门、档案库组织、索引 schema、无损 JSON、写入计划与执行、六条路由、快照与提示、两本词典一致、工具契约、客户端加载器契约、包清单、配置视图、样式 token 约束、声明层字段与队长段、schema 与常量对齐、v2 角色/组装/编译三层及其 schema 对齐、v1→v2 迁移、执行回执并入施工图)。
516
+ - `npm test` —— 315/315 通过(格式、解析、校验、扫描、路径、桥接、核对四桶、摘要与溯源、档案差异、能力要求与能力闸门、档案库组织、索引 schema、无损 JSON、写入计划与执行、六条路由、快照与提示、两本词典一致、工具契约、客户端加载器契约、包清单、配置视图、样式 token 约束、声明层字段与队长段、schema 与常量对齐、v2 角色/组装/编译三层及其 schema 对齐、v1→v2 迁移、执行回执并入施工图)。
505
517
  - `npm run smoke` —— 用真实 DSH bundle 的 `defineTool` 注册六个工具,逐个调用:输出全部通过无损校验、`path` 覆盖生效、释放干净;同一条命令随后用桩模块表加载 `client.js`,断言三处槽位注册、样式只注入一次、effect 可回收。
506
518
  - CLI:`validate --dir examples` 对两座示例库退出码 0。
507
519
  - 用插件管理器真的装进了本机 profile(`plugin_manager install_bundle` 指向本仓库 → `link:` 依赖 + bundle 条目),重启后按链接读取本仓库文件。
package/client.js CHANGED
@@ -660,13 +660,147 @@ window.__ModuleLoader__.load({
660
660
  );
661
661
  }
662
662
 
663
+ /**
664
+ * 从档案创建团队:挑一个队长会话,把建团提示排进去。
665
+ *
666
+ * 为什么不是“插件直接建”:团队只能由队长会话里的 `agent_teams_create` 建,插件没有这个权限。
667
+ * 所以这个对话框做的是端到端的前半段——把成员、队长模型、建团步骤一次交给一个会话,
668
+ * 后半段(建队、加成员、核对)在那个会话里发生;结果回团队页签与 `roster_verify` 看。
669
+ */
670
+ function TeamCreateDialog({ roster, sessions, onClose, onToast, onCreated }) {
671
+ useTranslation();
672
+ // 默认新会话:快速建团不该劫持用户正在聊的这个。
673
+ const [mode, setMode] = useState("new");
674
+ const [sessionId, setSessionId] = useState(sessions[0]?.sessionId ?? "");
675
+ const [cwd, setCwd] = useState(roster.rootDir ?? "");
676
+ const [teamName, setTeamName] = useState(roster.id);
677
+ const [busy, setBusy] = useState(false);
678
+ const [error, setError] = useState(null);
679
+ const captain = roster.captain ?? null;
680
+ const declaredModel =
681
+ captain !== null && captain.provider !== undefined && captain.model !== undefined && captain.provider !== "" && captain.model !== ""
682
+ ? `${captain.provider}/${captain.model}`
683
+ : null;
684
+ const canSubmit = !busy && (mode === "new" || sessionId !== "");
685
+
686
+ async function submit() {
687
+ if (!canSubmit) return;
688
+ setBusy(true);
689
+ setError(null);
690
+ try {
691
+ const payload = { rosterId: roster.id, teamName: teamName.trim(), mode };
692
+ if (mode === "existing") payload.sessionId = sessionId;
693
+ else if (cwd.trim() !== "") payload.cwd = cwd.trim();
694
+ const response = await readJson(APPLY_ROUTE, {
695
+ method: "POST",
696
+ headers: { "content-type": "application/json" },
697
+ body: JSON.stringify(payload),
698
+ });
699
+ if (!response.ok || response.body?.ok !== true) {
700
+ setError(response.body?.code ?? response.body?.error?.code ?? String(response.status));
701
+ return;
702
+ }
703
+ onToast(t("teamCreate.queued", { session: response.body.sessionId }));
704
+ onCreated?.(response.body.sessionId);
705
+ onClose();
706
+ } catch {
707
+ setError("network");
708
+ } finally {
709
+ setBusy(false);
710
+ }
711
+ }
712
+
713
+ return h(
714
+ Modal,
715
+ {
716
+ title: t("teamCreate.title", { roster: roster.id }),
717
+ onClose,
718
+ footer: h(
719
+ "div",
720
+ { className: "rstr-modal-foot-actions" },
721
+ h("button", { type: "button", className: "rstr-btn", onClick: onClose }, t("teamCreate.cancel")),
722
+ h(
723
+ "button",
724
+ { type: "button", className: "rstr-btn rstr-btn-primary", disabled: !canSubmit, onClick: submit },
725
+ busy ? t("teamCreate.creating") : t("teamCreate.submit"),
726
+ ),
727
+ ),
728
+ },
729
+ h("p", { className: "rstr-hint" }, t("teamCreate.hint")),
730
+ h(
731
+ Field,
732
+ { label: t("teamCreate.teamName") },
733
+ h("input", {
734
+ className: "rstr-input",
735
+ value: teamName,
736
+ onChange: (event) => setTeamName(event.target.value),
737
+ placeholder: roster.id,
738
+ }),
739
+ ),
740
+ h(
741
+ Field,
742
+ { label: t("teamCreate.target") },
743
+ h(
744
+ "div",
745
+ { className: "rstr-segmented" },
746
+ h(
747
+ "button",
748
+ { type: "button", className: mode === "new" ? "rstr-seg rstr-seg-on" : "rstr-seg", onClick: () => setMode("new") },
749
+ t("teamCreate.newSession"),
750
+ ),
751
+ h(
752
+ "button",
753
+ {
754
+ type: "button",
755
+ className: mode === "existing" ? "rstr-seg rstr-seg-on" : "rstr-seg",
756
+ onClick: () => setMode("existing"),
757
+ },
758
+ t("teamCreate.existingSession"),
759
+ ),
760
+ ),
761
+ mode === "existing"
762
+ ? h(
763
+ "select",
764
+ { className: "rstr-select", value: sessionId, onChange: (event) => setSessionId(event.target.value) },
765
+ sessions.length === 0
766
+ ? h("option", { value: "" }, t("teamCreate.noSessions"))
767
+ : sessions.map((session) =>
768
+ h(
769
+ "option",
770
+ { key: session.sessionId, value: session.sessionId },
771
+ `${session.sessionId.slice(0, 12)} · ${session.cwd || "?"}${
772
+ session.model === null ? "" : ` · ${session.model.model}`
773
+ }`,
774
+ ),
775
+ ),
776
+ )
777
+ : h("input", {
778
+ className: "rstr-input",
779
+ placeholder: t("teamCreate.cwdPlaceholder"),
780
+ value: cwd,
781
+ onChange: (event) => setCwd(event.target.value),
782
+ }),
783
+ ),
784
+ h(
785
+ "p",
786
+ { className: "rstr-hint" },
787
+ t("teamCreate.summary", {
788
+ count: roster.members.length,
789
+ model: declaredModel ?? t("teamCreate.noModel"),
790
+ }),
791
+ ),
792
+ captain === null ? h("p", { className: "rstr-hint" }, t("teamCreate.noCaptain")) : null,
793
+ error !== null ? h("p", { className: "rstr-bad rstr-small" }, t("teamCreate.failed", { code: error })) : null,
794
+ );
795
+ }
796
+
663
797
  /**
664
798
  * 一行 = 一份档案。
665
799
  *
666
800
  * 名册是列表不是画廊:id 立在第一列当"脊",状态长在左沿,操作平时只露两个入口。
667
801
  * 复制/建队/删除这些低频且危险的动作收进展开区——否则 21 份档案就是 105 个按钮。
668
802
  */
669
- function RosterCard({ roster, deployment, captainReport, sessions, onToast, onEdit, onDuplicate, onDelete, onCopy }) {
803
+ function RosterCard({ roster, deployment, captainReport, sessions, onToast, onEdit, onDuplicate, onDelete, onCopy, onCreateTeam }) {
670
804
  const [open, setOpen] = useState(false);
671
805
  const diagnostics = roster.diagnostics ?? [];
672
806
  const errors = diagnostics.filter((item) => item?.severity === "error").length;
@@ -717,6 +851,11 @@ window.__ModuleLoader__.load({
717
851
  h(
718
852
  "div",
719
853
  { className: "rstr-row-actions" },
854
+ h(
855
+ "button",
856
+ { type: "button", className: "rstr-btn rstr-btn-sm", onClick: () => onCreateTeam(roster) },
857
+ t("roster.createTeam"),
858
+ ),
720
859
  h("button", { type: "button", className: "rstr-btn rstr-btn-sm rstr-btn-quiet", onClick: () => onEdit(roster) }, t("roster.edit")),
721
860
  h(
722
861
  "button",
@@ -1373,14 +1512,19 @@ window.__ModuleLoader__.load({
1373
1512
  map.set(key, {
1374
1513
  key,
1375
1514
  scope: entry.scope,
1376
- title: entry.scope === "local" ? t("catalog.local") : entry.collectionTitle ?? entry.collectionId ?? "",
1515
+ id: entry.scope === "local" ? t("catalog.local") : entry.collectionId ?? "",
1516
+ title: entry.scope === "local" ? "" : entry.collectionTitle ?? "",
1377
1517
  entries: [],
1378
1518
  });
1379
1519
  }
1380
1520
  map.get(key).entries.push(entry);
1381
1521
  }
1382
- return [...map.values()];
1383
- }, [filtered, mode]);
1522
+ // 空间头上只放两件能当场核对的事实:有几份、其中几份本机已经有。
1523
+ return [...map.values()].map((group) => ({
1524
+ ...group,
1525
+ installed: group.entries.filter((entry) => installed.has(entry.rosterId)).length,
1526
+ }));
1527
+ }, [filtered, mode, installed, t]);
1384
1528
 
1385
1529
  function toggleTag(tag) {
1386
1530
  setSelected((current) => {
@@ -1419,15 +1563,16 @@ window.__ModuleLoader__.load({
1419
1563
  { className: "rstr-row-stats" },
1420
1564
  h("span", { className: "rstr-stat" }, t("catalog.members", { count: String(entry.members ?? 0) })),
1421
1565
  entry.scope === "remote" && installed.has(entry.rosterId)
1422
- ? h(Badge, { tone: "ok" }, t("catalog.installed"))
1566
+ ? h("span", { className: "rstr-row-here" }, t("catalog.here"))
1423
1567
  : null,
1424
- h(
1425
- "span",
1426
- { className: "rstr-stat" },
1427
- entry.scope === "local"
1428
- ? t("catalog.local")
1429
- : t("catalog.remoteFrom", { collection: entry.collectionTitle ?? entry.collectionId ?? "" }),
1430
- ),
1568
+ // 本机组里每行再写一次「本机」是废话:所在的分组头已经说了。
1569
+ entry.scope === "local"
1570
+ ? null
1571
+ : h(
1572
+ "span",
1573
+ { className: "rstr-stat" },
1574
+ t("catalog.remoteFrom", { collection: entry.collectionTitle ?? entry.collectionId ?? "" }),
1575
+ ),
1431
1576
  ),
1432
1577
  h(
1433
1578
  "div",
@@ -1439,7 +1584,8 @@ window.__ModuleLoader__.load({
1439
1584
  className: "rstr-btn rstr-btn-sm",
1440
1585
  disabled: busy,
1441
1586
  onClick: async () => {
1442
- if (entry.scope === "local") {
1587
+ // 已经在本机的那份,按钮就该是「打开」——不能让人去拉一份自己已经有的。
1588
+ if (entry.scope === "local" || installed.has(entry.rosterId)) {
1443
1589
  onOpen(entry.rosterId);
1444
1590
  return;
1445
1591
  }
@@ -1452,7 +1598,7 @@ window.__ModuleLoader__.load({
1452
1598
  }
1453
1599
  },
1454
1600
  },
1455
- entry.scope === "local" ? t("catalog.open") : t("catalog.pull"),
1601
+ entry.scope === "local" || installed.has(entry.rosterId) ? t("catalog.open") : t("catalog.pull"),
1456
1602
  ),
1457
1603
  ),
1458
1604
  ),
@@ -1549,14 +1695,22 @@ window.__ModuleLoader__.load({
1549
1695
  : groups.map((group) =>
1550
1696
  h(
1551
1697
  "div",
1552
- { key: group.key, className: "rstr-group" },
1553
- mode === "flat" || group.title === ""
1698
+ { key: group.key, className: "rstr-space" },
1699
+ mode === "flat"
1554
1700
  ? null
1555
1701
  : h(
1556
1702
  "div",
1557
- { className: "rstr-group-head" },
1558
- h("span", { className: "rstr-group-title" }, group.title),
1559
- h("span", { className: "rstr-group-count" }, String(group.entries.length)),
1703
+ { className: "rstr-space-head" },
1704
+ h("code", { className: "rstr-space-id", title: group.id }, group.id),
1705
+ h("span", { className: "rstr-space-title" }, group.title),
1706
+ h(
1707
+ "span",
1708
+ { className: "rstr-space-facts" },
1709
+ t("catalog.facts", {
1710
+ count: String(group.entries.length),
1711
+ installed: String(group.installed),
1712
+ }),
1713
+ ),
1560
1714
  group.scope === "remote" && typeof onPullSpace === "function"
1561
1715
  ? h(
1562
1716
  "button",
@@ -1594,6 +1748,7 @@ window.__ModuleLoader__.load({
1594
1748
  const [editor, setEditor] = useState(null);
1595
1749
  const [importing, setImporting] = useState(false);
1596
1750
  const [removing, setRemoving] = useState(null);
1751
+ const [creatingTeam, setCreatingTeam] = useState(null);
1597
1752
  const [toast, setToast] = useState(null);
1598
1753
 
1599
1754
  useEffect(() => {
@@ -1921,6 +2076,7 @@ window.__ModuleLoader__.load({
1921
2076
  onDuplicate: () => setEditor({ roster, rootDir: roster.rootDir, duplicate: true }),
1922
2077
  onDelete: () => setRemoving(roster),
1923
2078
  onCopy: copyPayload,
2079
+ onCreateTeam: (target) => setCreatingTeam(target),
1924
2080
  }),
1925
2081
  ),
1926
2082
  ),
@@ -2015,6 +2171,18 @@ window.__ModuleLoader__.load({
2015
2171
  },
2016
2172
  })
2017
2173
  : null,
2174
+ creatingTeam !== null
2175
+ ? h(TeamCreateDialog, {
2176
+ roster: creatingTeam,
2177
+ sessions: snapshot?.sessions?.sessions ?? [],
2178
+ onClose: () => setCreatingTeam(null),
2179
+ onToast: setToast,
2180
+ onCreated: () => {
2181
+ setCreatingTeam(null);
2182
+ library.reload();
2183
+ },
2184
+ })
2185
+ : null,
2018
2186
  removing !== null
2019
2187
  ? h(DeleteDialog, {
2020
2188
  roster: removing,
@@ -2735,13 +2903,37 @@ window.__ModuleLoader__.load({
2735
2903
  }
2736
2904
  .rstr-catalog .rstr-search { max-width: 560px; }
2737
2905
 
2738
- .rstr-group { display: flex; flex-direction: column; min-width: 0; }
2906
+ .rstr-space { display: flex; flex-direction: column; min-width: 0; }
2907
+ .rstr-space + .rstr-space { margin-top: 24px; }
2908
+ .rstr-space-head {
2909
+ display: grid;
2910
+ grid-template-columns: minmax(110px, 176px) minmax(0, 1fr) auto auto;
2911
+ align-items: baseline;
2912
+ gap: 8px 16px;
2913
+ padding: 16px 8px 6px 0;
2914
+ border-bottom: 1px solid var(--dsw-alias-border-l2);
2915
+ min-width: 0;
2916
+ }
2917
+ .rstr-space-id {
2918
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
2919
+ font-size: 13px;
2920
+ font-weight: 600;
2921
+ color: var(--dsw-alias-label-primary);
2922
+ overflow: hidden;
2923
+ text-overflow: ellipsis;
2924
+ white-space: nowrap;
2925
+ }
2926
+ .rstr-space-title { font-size: 13px; color: var(--dsw-alias-label-secondary); min-width: 0; }
2927
+ .rstr-space-facts {
2928
+ font-size: 11px;
2929
+ color: var(--dsw-alias-label-tertiary);
2930
+ font-variant-numeric: tabular-nums;
2931
+ white-space: nowrap;
2932
+ }
2933
+ .rstr-row-here { font-size: 11px; color: var(--dsw-alias-label-tertiary); }
2739
2934
 
2740
- .rstr-group-head { display: flex; align-items: baseline; gap: 8px; padding: 10px 0 4px; border-bottom: 1px solid var(--dsw-alias-border-l2); }
2741
2935
 
2742
- .rstr-group-title { font-size: 12px; font-weight: 600; letter-spacing: 0.02em; color: var(--dsw-alias-label-secondary); }
2743
2936
 
2744
- .rstr-group-count { font-size: 11px; color: var(--dsw-alias-label-tertiary); font-variant-numeric: tabular-nums; }
2745
2937
 
2746
2938
  .rstr-results { display: flex; flex-direction: column; min-width: 0; padding: 0 28px 28px; border-top: 1px solid var(--dsw-alias-border-l2); }
2747
2939
 
@@ -2869,6 +3061,8 @@ window.__ModuleLoader__.load({
2869
3061
  .rstr-apply { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
2870
3062
  .rstr-segmented { display: inline-flex; border: 1px solid var(--dsw-alias-border-l3); }
2871
3063
  .rstr-seg { border: none; background: transparent; padding: 5px 12px; font-size: 12px; font-family: inherit; cursor: pointer; color: var(--dsw-alias-label-secondary); }
3064
+ /* 对话框里的二选一开关:字段列默认拉伸,这里收回内容宽度,别让它长得像第二个输入框。 */
3065
+ .rstr-field > .rstr-segmented { align-self: flex-start; }
2872
3066
  .rstr-seg:hover { background: var(--dsw-alias-interactive-bg-hover); }
2873
3067
  .rstr-seg-on { background: color-mix(in srgb, var(--dsw-alias-state-business-primary) 10%, transparent); color: var(--dsw-alias-label-primary); font-weight: 600; }
2874
3068
 
@@ -3080,6 +3274,16 @@ window.__ModuleLoader__.load({
3080
3274
  .rstr-tag { border-left: none; border-bottom: 2px solid transparent; border-radius: 0; white-space: nowrap; }
3081
3275
  .rstr-tag-on { border-left: none; border-bottom-color: var(--dsw-alias-state-business-primary); background: transparent; }
3082
3276
  .rstr-tag-clear { margin-top: 0; flex: 0 0 auto; }
3277
+ /* 空间头:窄屏由一列 id/标题/事实/按钮的四列网格折成两行 */
3278
+ .rstr-space-head {
3279
+ grid-template-columns: minmax(0, 1fr) auto;
3280
+ row-gap: 2px;
3281
+ padding-top: 14px;
3282
+ }
3283
+ .rstr-space-id { grid-area: 1 / 1; }
3284
+ .rstr-space-head > .rstr-btn { grid-area: 1 / 2; }
3285
+ .rstr-space-title { grid-area: 2 / 1; }
3286
+ .rstr-space-facts { grid-area: 2 / 2; text-align: right; }
3083
3287
  .rstr-row-body { grid-template-columns: minmax(0, 1fr) auto; }
3084
3288
  .rstr-row-id { grid-column: 1; }
3085
3289
  .rstr-row-text { grid-column: 1; }
package/index.js CHANGED
@@ -441,6 +441,8 @@ export function apply(ctx, config) {
441
441
  mode: input.mode === "new" ? "new" : "existing",
442
442
  sessionId: typeof input.sessionId === "string" ? input.sessionId : "",
443
443
  cwd: typeof input.cwd === "string" ? input.cwd : "",
444
+ // 团队名只进提示词,不写任何团队状态;超长按 120 截断,与档案标题同量级。
445
+ teamName: typeof input.teamName === "string" ? input.teamName.trim().slice(0, 120) : "",
444
446
  });
445
447
  const status = result.ok ? 200 : result.code === "session-controller-unavailable" ? 503 : 400;
446
448
  sendJson(res, status, result);
package/locale/en.json CHANGED
@@ -86,7 +86,8 @@
86
86
  "showMembers": "Show members",
87
87
  "hideMembers": "Hide members",
88
88
  "warnings": "{count} warnings",
89
- "deployedDrifted": "In {teams} ({count} drifted)"
89
+ "deployedDrifted": "In {teams} ({count} drifted)",
90
+ "createTeam": "Create team"
90
91
  },
91
92
  "member": {
92
93
  "prompt": "Prompt",
@@ -320,6 +321,26 @@
320
321
  "refresh": "Refresh",
321
322
  "installed": "Installed",
322
323
  "pullSpace": "Pull whole space",
323
- "pulledSpace": "Space pulled: {files} files written, {skipped} skipped"
324
+ "pulledSpace": "Space pulled: {files} files written, {skipped} skipped",
325
+ "facts": "{count} rosters · {installed} here",
326
+ "here": "Already here"
327
+ },
328
+ "teamCreate": {
329
+ "title": "Create a team from {roster}",
330
+ "hint": "The plugin never writes team state: it queues a build prompt into the captain session, which then calls agent_teams_create and adds each member.",
331
+ "teamName": "Team name",
332
+ "target": "Captain session",
333
+ "newSession": "New session",
334
+ "existingSession": "Existing session",
335
+ "noSessions": "No session available",
336
+ "cwdPlaceholder": "Working directory (empty = current workspace)",
337
+ "summary": "{count} members · captain model {model}",
338
+ "noModel": "not declared, the session keeps its model",
339
+ "noCaptain": "This roster has no captain block: it can still build a team, but the captain model is not switched.",
340
+ "submit": "Create",
341
+ "creating": "Queueing…",
342
+ "cancel": "Cancel",
343
+ "queued": "Queued into session {session} — that session will build the team.",
344
+ "failed": "Could not queue ({code})"
324
345
  }
325
346
  }
package/locale/zh.json CHANGED
@@ -86,7 +86,8 @@
86
86
  "showMembers": "展开成员",
87
87
  "hideMembers": "收起成员",
88
88
  "warnings": "{count} 条提醒",
89
- "deployedDrifted": "在 {teams}({count} 处不一致)"
89
+ "deployedDrifted": "在 {teams}({count} 处不一致)",
90
+ "createTeam": "创建团队"
90
91
  },
91
92
  "member": {
92
93
  "prompt": "人设",
@@ -320,6 +321,26 @@
320
321
  "refresh": "刷新",
321
322
  "installed": "已安装",
322
323
  "pullSpace": "拉取整个空间",
323
- "pulledSpace": "空间已拉取:写入 {files} 个文件,跳过 {skipped} 个"
324
+ "pulledSpace": "空间已拉取:写入 {files} 个文件,跳过 {skipped} 个",
325
+ "facts": "{count} 份 · 已装 {installed}",
326
+ "here": "已在本机"
327
+ },
328
+ "teamCreate": {
329
+ "title": "从 {roster} 创建团队",
330
+ "hint": "插件不写团队状态:它只把建团提示排进队长会话,由那个会话调用 agent_teams_create 并逐个加成员。",
331
+ "teamName": "团队名",
332
+ "target": "队长会话",
333
+ "newSession": "新会话",
334
+ "existingSession": "已有会话",
335
+ "noSessions": "没有可选的会话",
336
+ "cwdPlaceholder": "工作目录(留空用当前工作区)",
337
+ "summary": "将建 {count} 名成员 · 队长模型 {model}",
338
+ "noModel": "未声明,沿用会话当前模型",
339
+ "noCaptain": "这份档案没有 captain 段:仍可建团,但不会切换队长模型。",
340
+ "submit": "创建",
341
+ "creating": "排队中…",
342
+ "cancel": "取消",
343
+ "queued": "已排入会话 {session}——那个会话会开始建队。",
344
+ "failed": "排队失败({code})"
324
345
  }
325
346
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stofone/dsh-agent-roster",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "description": "多 Agent 团队的静态成员档案定义与校验层:档案库在面板里看得见、改得动,团队成员的人设、模型与工具边界固化成可校验、可版本化的文件,并在 dsh-agent-teams 创建成员时把档案桥接给运行时。",
5
5
  "type": "module",
6
6
  "main": "index.js",