@wowyuarm/dsh-agent-team 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/README.md +8 -1
  2. package/README.zh.md +9 -1
  3. package/package.json +12 -3
  4. package/packages/agent-team/README.md +3 -3
  5. package/packages/agent-team/README.zh.md +3 -3
  6. package/packages/agent-team/lib/attachments.js +11 -12
  7. package/packages/agent-team/lib/auto-compaction.js +163 -0
  8. package/packages/agent-team/lib/index.js +252 -139
  9. package/packages/agent-team/lib/ledger.js +344 -170
  10. package/packages/agent-team/lib/recovery.js +30 -35
  11. package/packages/agent-team/lib/spec.js +27 -11
  12. package/packages/agent-team/lib/typert.host.js +197 -88
  13. package/packages/agent-team/lib/typert.remote-client.d.ts +3 -1
  14. package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
  15. package/packages/agent-team/lib/typert.remote-client.js +139 -59
  16. package/packages/agent-team/lib/types/attachments.d.ts +2 -2
  17. package/packages/agent-team/lib/types/attachments.d.ts.map +1 -1
  18. package/packages/agent-team/lib/types/auto-compaction.d.ts +38 -0
  19. package/packages/agent-team/lib/types/auto-compaction.d.ts.map +1 -0
  20. package/packages/agent-team/lib/types/index.d.ts +52 -19
  21. package/packages/agent-team/lib/types/index.d.ts.map +1 -1
  22. package/packages/agent-team/lib/types/ledger.d.ts +29 -2
  23. package/packages/agent-team/lib/types/ledger.d.ts.map +1 -1
  24. package/packages/agent-team/lib/types/recovery.d.ts +15 -16
  25. package/packages/agent-team/lib/types/recovery.d.ts.map +1 -1
  26. package/packages/agent-team/lib/types/spec.d.ts.map +1 -1
  27. package/packages/agent-team/lib/types/types.d.ts +69 -30
  28. package/packages/agent-team/lib/types/types.d.ts.map +1 -1
  29. package/packages/agent-team/preset/team-member/agent.cordis.yml +7 -2
  30. package/packages/client-agent-team/README.md +2 -1
  31. package/packages/client-agent-team/README.zh.md +2 -1
  32. package/packages/client-agent-team/lib/client.js +1577 -863
  33. package/packages/client-agent-team/lib/client.js.map +1 -1
  34. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts.map +1 -1
  35. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.js +35 -174
  36. package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts.map +1 -1
  37. package/packages/client-agent-team/lib/types/client/TeamChannelPage.js +20 -30
  38. package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.d.ts.map +1 -1
  39. package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.js +3 -2
  40. package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts +6 -1
  41. package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts.map +1 -1
  42. package/packages/client-agent-team/lib/types/client/TeamComposer.js +4 -5
  43. package/packages/client-agent-team/lib/types/client/TeamConversation.d.ts +1 -1
  44. package/packages/client-agent-team/lib/types/client/TeamConversation.d.ts.map +1 -1
  45. package/packages/client-agent-team/lib/types/client/TeamConversation.js +3 -3
  46. package/packages/client-agent-team/lib/types/client/TeamMemberEditor.d.ts +34 -0
  47. package/packages/client-agent-team/lib/types/client/TeamMemberEditor.d.ts.map +1 -0
  48. package/packages/client-agent-team/lib/types/client/TeamMemberEditor.js +169 -0
  49. package/packages/client-agent-team/lib/types/client/TeamMemberSessionComposer.d.ts +17 -0
  50. package/packages/client-agent-team/lib/types/client/TeamMemberSessionComposer.d.ts.map +1 -0
  51. package/packages/client-agent-team/lib/types/client/TeamMemberSessionComposer.js +120 -0
  52. package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts +4 -1
  53. package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts.map +1 -1
  54. package/packages/client-agent-team/lib/types/client/TeamMessage.js +94 -47
  55. package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts +2 -1
  56. package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts.map +1 -1
  57. package/packages/client-agent-team/lib/types/client/TeamThreadPage.js +81 -39
  58. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts.map +1 -1
  59. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.js +2 -1
  60. package/packages/client-agent-team/lib/types/client/index.d.ts.map +1 -1
  61. package/packages/client-agent-team/lib/types/client/index.js +72 -1
  62. package/packages/client-agent-team/lib/types/client/locales.d.ts +24 -0
  63. package/packages/client-agent-team/lib/types/client/locales.d.ts.map +1 -1
  64. package/packages/client-agent-team/lib/types/client/locales.js +24 -0
  65. package/packages/client-agent-team/lib/types/client/member-session-input.d.ts +22 -0
  66. package/packages/client-agent-team/lib/types/client/member-session-input.d.ts.map +1 -0
  67. package/packages/client-agent-team/lib/types/client/member-session-input.js +98 -0
  68. package/packages/client-agent-team/lib/types/client/navigation.d.ts +1 -1
  69. package/packages/client-agent-team/lib/types/client/navigation.d.ts.map +1 -1
  70. package/packages/client-agent-team/lib/types/client/navigation.js +12 -6
  71. package/packages/client-agent-team/lib/types/client/requests.d.ts +16 -0
  72. package/packages/client-agent-team/lib/types/client/requests.d.ts.map +1 -0
  73. package/packages/client-agent-team/lib/types/client/requests.js +22 -0
  74. package/packages/client-agent-team/lib/types/client/slots.d.ts +2 -1
  75. package/packages/client-agent-team/lib/types/client/slots.d.ts.map +1 -1
  76. package/packages/client-agent-team/lib/types/client/task-refs.d.ts +6 -1
  77. package/packages/client-agent-team/lib/types/client/task-refs.d.ts.map +1 -1
  78. package/packages/client-agent-team/lib/types/client/task-refs.js +19 -0
  79. package/packages/client-agent-team/lib/types/client/team-formatters.d.ts +52 -3
  80. package/packages/client-agent-team/lib/types/client/team-formatters.d.ts.map +1 -1
  81. package/packages/client-agent-team/lib/types/client/team-formatters.js +74 -4
  82. package/packages/client-agent-team/lib/types/client/team-membership.d.ts.map +1 -1
  83. package/packages/client-agent-team/lib/types/client/team-membership.js +2 -1
  84. package/packages/tool-agent-team/README.md +7 -7
  85. package/packages/tool-agent-team/README.zh.md +7 -7
  86. package/packages/tool-agent-team/lib/index.js +38 -28
  87. package/packages/tool-agent-team/lib/types/index.d.ts.map +1 -1
  88. package/packages/client-agent-team/lib/types/client/TeamInboxPanel.d.ts +0 -13
  89. package/packages/client-agent-team/lib/types/client/TeamInboxPanel.d.ts.map +0 -1
  90. package/packages/client-agent-team/lib/types/client/TeamInboxPanel.js +0 -90
package/README.md CHANGED
@@ -5,6 +5,7 @@
5
5
  [![npm](https://img.shields.io/npm/v/@wowyuarm/dsh-agent-team?style=flat-square)](https://www.npmjs.com/package/@wowyuarm/dsh-agent-team)
6
6
  [![License](https://img.shields.io/badge/license-MIT-blue?style=flat-square)](LICENSE)
7
7
  [![Release](https://img.shields.io/github/v/release/wowyuarm/dsh-agent-team?include_prereleases&style=flat-square)](https://github.com/wowyuarm/dsh-agent-team/releases)
8
+ [![Listed on Awesome DSH Plugin](https://awesome-dsh-plugin.com/badge.svg)](https://awesome-dsh-plugin.com/p/wowyuarm/dsh-agent-team/)
8
9
 
9
10
  **dsh-agent-team** helps a human manage tasks in an orderly way and use agents as true collaborators: agents are persistent identities for their sessions; Workspaces organize agents and sessions per project; Channels carry responsibilities; Task Threads chain session agents into one line of progress.
10
11
 
@@ -12,7 +13,13 @@ An opt-in plugin for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-
12
13
 
13
14
  ## Preview
14
15
 
15
- ![Team mode in the DSH Web UI: workspaces, channels, and agents in the sidebar; a Task thread with agent reports](assets/readme/team-mode.png)
16
+ ![Team mode in the DSH Web UI: Channels and seven online Agents in the sidebar; the Main Channel shows Task references and the composer’s task switch](assets/readme/team-mode.png)
17
+
18
+ ### Task Threads
19
+
20
+ A Task Thread keeps Claims, Agent handoffs, Human acceptance, and follow-up replies in one durable context.
21
+
22
+ ![Completed Task Thread in the DSH Web UI: a Claim, Agent handoffs, Human acceptance activity, and the reply composer](assets/readme/task-thread.png)
16
23
 
17
24
  ## Quick start
18
25
 
package/README.zh.md CHANGED
@@ -2,13 +2,21 @@
2
2
 
3
3
  [English](README.md) | 简体中文
4
4
 
5
+ [![Listed on Awesome DSH Plugin](https://awesome-dsh-plugin.com/badge.svg)](https://awesome-dsh-plugin.com/p/wowyuarm/dsh-agent-team/)
6
+
5
7
  **dsh-agent-team** 帮助 human 有序管理任务、把 agents 用成真正的协作者:Agent 是 session 的持久身份;Workspace 按项目组织 agents 与 sessions;Channel 承载职责分派;Task Thread 把多个 session agent 串成一条推进线。
6
8
 
7
9
  一个为 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 提供的按需启用插件:只在需要 Team mode 的 profile 安装,普通 DSH Session 保持原有 preset roster。
8
10
 
9
11
  ## 预览
10
12
 
11
- ![DSH Web UI 中的 Team mode:侧边栏是 Workspace、频道与 Agent;主区是一个 Task Thread,含 Agent 汇报](assets/readme/team-mode.png)
13
+ ![DSH Web UI 中的 Team mode:侧边栏展示频道与 7 位在线 Agent;Main 频道含 Task 引用和 composer 的作为任务开关](assets/readme/team-mode.png)
14
+
15
+ ### Task Thread
16
+
17
+ Task Thread 把 Claim、Agent 交接、Human 验收和后续回复保留在同一条可持续阅读的上下文中。
18
+
19
+ ![DSH Web UI 中已完成的 Task Thread:含 Claim、Agent 交接、Human 验收活动和回复 composer](assets/readme/task-thread.png)
12
20
 
13
21
  ## 快速开始
14
22
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wowyuarm/dsh-agent-team",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Help humans organize tasks and let agents collaborate - a Team plugin for DeepSeek Harness",
5
5
  "repository": {
6
6
  "type": "git",
@@ -18,15 +18,17 @@
18
18
  "README.zh.md"
19
19
  ],
20
20
  "scripts": {
21
- "generate:typert": "cd ../deepseek-harness && node --import tsx ../dsh-agent-team/scripts/generate-typert.mjs",
21
+ "generate:typert": "node --import tsx scripts/generate-typert.mjs",
22
22
  "prepack": "npm run build",
23
+ "clean:build-outputs": "node scripts/clean-build-outputs.mjs",
23
24
  "test": "npm run generate:typert && vitest run",
24
25
  "test:browser": "npm run build && node scripts/run-browser-test.mjs",
25
26
  "preview": "node scripts/check-preview-credentials.mjs && npm run build && node scripts/run-preview.mjs",
26
27
  "preview:ui": "npm run build && node scripts/run-ui-preview.mjs",
27
28
  "lint": "corepack pnpm dlx oxlint@1.76.0 --disable-typescript-plugin packages scripts",
29
+ "duplication": "jscpd --config .jscpd.json packages scripts",
28
30
  "typecheck": "npm run generate:typert && tsc -p packages/agent-team/tsconfig.json && tsc -p packages/tool-agent-team/tsconfig.json && tsc -p packages/client-agent-team/tsconfig.json",
29
- "build": "npm run generate:typert && tsc -p packages/agent-team/tsconfig.build.json && tsc -p packages/tool-agent-team/tsconfig.build.json && tsc -p packages/client-agent-team/tsconfig.build.json && node scripts/build-client.mjs"
31
+ "build": "npm run clean:build-outputs && npm run generate:typert && tsc -p packages/agent-team/tsconfig.build.json && tsc -p packages/tool-agent-team/tsconfig.build.json && tsc -p packages/client-agent-team/tsconfig.build.json && node scripts/build-client.mjs"
30
32
  },
31
33
  "dsh": {
32
34
  "bundle": {
@@ -41,6 +43,7 @@
41
43
  "@deepseek-ai/dsh-client-ui-layout",
42
44
  "@deepseek-ai/dsh-client-ui-settings-general",
43
45
  "@deepseek-ai/dsh-client-ui-conversation",
46
+ "@deepseek-ai/dsh-client-ui-input-trigger",
44
47
  "@deepseek-ai/dsh-client-ui-workspace",
45
48
  "@deepseek-ai/dsh-client-locale"
46
49
  ]
@@ -56,9 +59,11 @@
56
59
  "@types/node": "^24.0.0",
57
60
  "@types/react": "~18.3.31",
58
61
  "@types/react-dom": "~18.3.7",
62
+ "jscpd": "^5.0.16",
59
63
  "jsdom": "^30.0.1",
60
64
  "react": "^18.3.1",
61
65
  "react-dom": "^18.3.1",
66
+ "tsx": "^4.22.4",
62
67
  "typescript": "^5.8.3",
63
68
  "use-sync-external-store": "1.2.0",
64
69
  "vite": "^7.0.0",
@@ -121,12 +126,15 @@
121
126
  "@deepseek-ai/dsh-client-locale": ">=0.1.1-rc.2 <0.2.0",
122
127
  "@deepseek-ai/dsh-client-runtime": ">=0.1.1-rc.2 <0.2.0",
123
128
  "@deepseek-ai/dsh-client-ui-conversation": ">=0.1.1-rc.2 <0.2.0",
129
+ "@deepseek-ai/dsh-client-ui-input-trigger": ">=0.1.1-rc.2 <0.2.0",
124
130
  "@deepseek-ai/dsh-client-ui-layout": ">=0.1.1-rc.2 <0.2.0",
125
131
  "@deepseek-ai/dsh-client-ui-primitives": ">=0.1.1-rc.2 <0.2.0",
126
132
  "@deepseek-ai/dsh-client-ui-settings-general": ">=0.1.1-rc.2 <0.2.0",
127
133
  "@deepseek-ai/dsh-client-ui-sidebar": ">=0.1.1-rc.2 <0.2.0",
128
134
  "@deepseek-ai/dsh-client-ui-slots": ">=0.1.1-rc.2 <0.2.0",
129
135
  "@deepseek-ai/dsh-client-ui-workspace": ">=0.1.1-rc.2 <0.2.0",
136
+ "@deepseek-ai/dsh-command-compact": ">=0.1.1-rc.2 <0.2.0",
137
+ "@deepseek-ai/dsh-compaction": ">=0.1.1-rc.2 <0.2.0",
130
138
  "@deepseek-ai/dsh-home-paths": ">=0.1.1-rc.2 <0.2.0",
131
139
  "@deepseek-ai/dsh-invariants": ">=0.1.1-rc.2 <0.2.0",
132
140
  "@deepseek-ai/dsh-llm": ">=0.1.1-rc.2 <0.2.0",
@@ -136,6 +144,7 @@
136
144
  "@deepseek-ai/dsh-session-persistence": ">=0.1.1-rc.2 <0.2.0",
137
145
  "@deepseek-ai/dsh-skill-filesystem": ">=0.1.1-rc.2 <0.2.0",
138
146
  "@deepseek-ai/dsh-storage-domain": ">=0.1.1-rc.2 <0.2.0",
147
+ "@deepseek-ai/dsh-token-meter": ">=0.1.1-rc.2 <0.2.0",
139
148
  "@deepseek-ai/dsh-tool-bash": ">=0.1.1-rc.2 <0.2.0",
140
149
  "@deepseek-ai/dsh-tool-fs": ">=0.1.1-rc.2 <0.2.0",
141
150
  "@deepseek-ai/dsh-tool-fs-search": ">=0.1.1-rc.2 <0.2.0",
@@ -20,9 +20,9 @@ Member creation commits one stable Member/session/Workspace/preset/private-memor
20
20
 
21
21
  Every team-managed session records `danger-full-access`. Project cwd remains the Workspace path, while private memory lives under `$DSH_HOME/agent-team/members/<memberId>/`. An untitled Member session is named with its handle through the session-title service, so the ordinary Session list shows the Member identity; an explicit rename or any earlier title always wins. The isolated `team-member` preset also provides coding tools including model-facing web search, Workspace instruction discovery, and Team protocol guidance. The host owns the shared Web service and provider; the preset mounts only the model-facing web tool. Its lowercase `memory.md` is a bounded 8 KiB reference index, injected only for that Member when its content changes; `notes/` stays on-demand through filesystem tools. Over-budget indexes receive a maintenance warning, not silent truncation. Ordinary sessions and forks receive no Team identity or private-memory context. At Host startup the Host prunes `member:`-shaped private-memory directories that the replayed ledger does not reference — leftovers from discarded ledgers, since a version-bumped medium rejects at open and starts empty. Prune failures fail startup; entries outside the `member:` shape are left untouched.
22
22
 
23
- Adding a Member to a Channel grants future read/send/claim authority but injects no historical Messages into the member session. Thread Attention starts when a Task is created, a Claim is created, a member follows, a top-level Message mentions them, or a Human confirms an invitation. Ordinary unread is derived from Attention; structured mentions create durable direct markers, while terminal Task changes retain sparse Activity markers for affected followers after Attention ends. `team_inbox` and Thread reads are Host projections, not Session inbox contents. A direct mention context includes its Message body and source; Task/Claim changes include concise transition facts; ordinary unread includes only a body-free Task route. Pending hints are coalesced, ignored hints do not loop, and resume/error recovery derives a new hint from durable unread state.
23
+ Adding a Member to a Channel grants future read/send/claim authority but injects no historical Messages into the member session. Every top-level Message creates a Thread; new Client/tool starts explicitly choose taskless while released-client omission remains taskful, and a Human can later promote a taskless Thread by atomically adding its Task overlay and a structured `promote` Task activity. Thread Attention starts when a Thread is created, a Claim is created, a member follows, a top-level Message mentions them, or a Human confirms an invitation. Ordinary unread is derived from Attention; structured mentions create durable direct markers, while terminal Task changes retain sparse Activity markers for affected followers after Attention ends. `team_inbox` and Thread reads are Host projections, not Session inbox contents. A direct mention context includes its Message body and source; Task/Claim changes — including promotion — include concise transition facts; ordinary unread carries a body-free Thread-first route. Pending hints are coalesced, ignored hints do not loop, and resume/error recovery derives a new hint from durable unread state. For recoverable service failures, the Host wakes a Member after each of the first two consecutive `agent/error` occurrences, then leaves the third failure for the operator; only a clean turn resets that run.
24
24
 
25
- Member replies require the exact current Thread revision and atomically update Message and Thread facts. Unread work must be read before a mutation; stale revisions are rejected after that unread gate. A closed Task rejects replies and new Attention; reopening restores the Task without restoring prior Attention. Top-level Messages may mention Agents directly: mentioned Members start following the new Thread. In an existing Thread, a Human reply mentioning an unfollowed Agent requires a process-local, one-use confirmation token before the operation commits, while such Member replies are rejected with member_not_following. Message facts carry their structured mention refs, and the Client renders mention chips only for those Members. Claim/done/release and Task changes are ordered host-authored Activities. Active Claims exclude only the same normalized Direction; different Directions can run concurrently. Task state is derived from Claims unless Human acceptance or closure overrides it. Close releases active Claims and clears Thread Attention atomically. Member removal marks the member inactive, releases owned active Claims, clears its Attention and direct markers, and archives the session after the durable commit. Message and Activity facts share one bounded sequence cursor.
25
+ Member replies require the exact current Thread revision and atomically update Message and Thread facts. `threadRef` is the primary collaboration identity; released task-only Clients may send a Host-resolved `taskRef` alias for taskful Threads, while Task/Claim operations remain Task-ref based. Unread work must be read before a mutation; stale revisions are rejected after that unread gate. A closed Task rejects replies and new Attention; reopening restores the Task without restoring prior Attention. Taskless Threads still support replies, follow, mentions, Inbox, read, and history, but have no Claims or Task-resolution path. Top-level Messages may mention Agents directly: mentioned Members start following the new Thread. In an existing Thread, a Human reply mentioning an unfollowed Agent requires a process-local, one-use confirmation token before the operation commits, while such Member replies are rejected with member_not_following. Message facts carry their structured mention refs, and the Client renders mention chips only for those Members. Claim/done/release and Task changes are ordered host-authored Activities. Active Claims exclude only the same normalized Direction; different Directions can run concurrently. Task state is derived from Claims unless Human acceptance or closure overrides it. Close releases active Claims and clears Thread Attention atomically. Member removal marks the member inactive, releases owned active Claims, clears its Attention and direct markers, and archives the session after the durable commit. Message and Activity facts share one bounded sequence cursor.
26
26
 
27
27
  `changes()` is the Client invalidation stream. Each request declares one optional `scope` — workspace, channel, or thread — plus an abortable transport signal; a committed operation wakes only waiters whose scope matches the scopes derived from that operation (member lifecycle and presence wake their Workspace; content operations wake their Channel and Thread). A Thread read commits durably but derives no scope, because it advances only the reader's private watermark. After each commit the Host notifies only the Members whose Inbox projection the operation can have changed — the operation's Attention/marker delta plus current followers of the touched Threads — never every live Agent.
28
28
 
@@ -40,7 +40,7 @@ A team-enabled preset registers the five tools in its own agent scope and marks
40
40
 
41
41
  #### What the model sees
42
42
 
43
- An enabled Member may receive one bounded, coalesced context notification after durable unread work appears. Structured direct mentions include their Message body and source; Task/Claim Activities include concise state transitions; ordinary unread includes only Task refs, counts, and revisions. The notification is queued at the Agent's safe step boundary, so active model requests and tools are not interrupted. It routes the common path directly to `team_thread.read`; the model uses `team_inbox` only when it needs to triage remaining Threads.
43
+ An enabled Member may receive one bounded, coalesced context notification after durable unread work appears. Structured direct mentions include their Message body and source; Task/Claim Activities include concise state transitions; ordinary unread includes only a Thread-first route, counts, and revisions, with a Task overlay where present. The notification is queued at the Agent's safe step boundary, so active model requests and tools are not interrupted. It routes the common path directly to `team_thread.read`; the model uses `team_inbox` only when it needs to triage remaining Threads.
44
44
 
45
45
  #### Token effect
46
46
 
@@ -20,9 +20,9 @@ Service 使用 `ctx.storageDomain`、`ctx.workspaceRegistry`、`ctx.agents`、`c
20
20
 
21
21
  每个 Team 管理的 session 都会持久写入 `danger-full-access`。项目 cwd 仍是 Workspace 路径,私有记忆位于 `$DSH_HOME/agent-team/members/<memberId>/`。没有标题的 Member session 会通过 session-title service 以 handle 命名,普通 Session 列表因此直接显示 Member 身份;显式重命名或任何既有标题始终优先。隔离的 `team-member` preset 还提供 coding 工具、面向模型的 Web 搜索、Workspace instruction discovery 和 Team protocol guidance。共享的 Web service 与 provider 仍由 Host 持有;preset 只挂载面向模型的 Web tool。小写 `memory.md` 是有界的 8 KiB 参考索引,只有该 Member 的内容变化时才注入;`notes/` 只通过 filesystem tools 按需读取。超预算索引只产生维护警告,不静默截断。普通 session 和 fork 不获得 Team 身份或私有记忆上下文。Host 启动时会清理重放 ledger 不再引用的 `member:` 形态私有记忆目录——这些是 ledger 被丢弃后的遗留(版本提升的 medium 在 open 时被拒绝并以空状态重启)。清理失败会使启动失败;非 `member:` 形态的条目保持原样。
22
22
 
23
- 把 Member 加入 Channel 只授予之后的 read/send/claim authority,不向 Member session 注入历史 Message。创建 Task、创建 Claim、显式 follow、顶层消息 mention 到、或 Human 确认邀请会开始 Thread Attention。普通未读从 Attention 派生,structured mention 形成持久 direct marker;终止 Task 的状态变化会为受影响关注者保留稀疏 Activity marker,即使 Attention 已结束仍可读取。`team_inbox` 和 Thread read 是 Host projection,不是 Session inbox 内容。Direct mention context 包含消息正文和来源,Task/Claim 变化包含简短状态事实,普通未读只包含无正文的 Task 路由;提示会合并,忽略提示不会形成循环,resume/runtime error recovery 会从持久未读状态重新判断是否提示。
23
+ 把 Member 加入 Channel 只授予之后的 read/send/claim authority,不向 Member session 注入历史 Message。每条顶层 Message 都创建 Thread;新 Client/tool 显式选择 taskless,released Client 省略意图时仍保持 taskful;Human 可之后 promotion taskless Thread,原子附加 Task overlay 与结构化 `promote` Task activity。创建 Thread、创建 Claim、显式 follow、顶层消息 mention 到、或 Human 确认邀请会开始 Thread Attention。普通未读从 Attention 派生,structured mention 形成持久 direct marker;终止 Task 的状态变化会为受影响关注者保留稀疏 Activity marker,即使 Attention 已结束仍可读取。`team_inbox` 和 Thread read 是 Host projection,不是 Session inbox 内容。Direct mention context 包含消息正文和来源,Task/Claim 变化(含 promotion)包含简短状态事实,普通未读提供无正文的 Thread-first 路由。提示会合并,忽略提示不会形成循环,resume/runtime error recovery 会从持久未读状态重新判断是否提示。对于可恢复的服务错误,Host 会在连续 `agent/error` occurrence 的前两次后唤醒 Member;第 3 次错误则交给 operator,只有 clean turn 才会重置这段连续错误。
24
24
 
25
- Member reply 必须携带准确的当前 Thread revision,并在一个 operation 内更新 Message 和 Thread facts。未读工作必须先 read;revision 过期时拒绝写入。Closed Task 拒绝 reply 和新的 Attention;reopen 恢复 Task,但不恢复之前的 Attention。顶层消息可以直接 mention Agent:被 mention 的 Member 会开始关注新 Thread。在既有 Thread 中,Human 的 reply 提到 unfollowed Agent 时必须先取得 process-local one-use confirmation token 才提交 operation;Member 的此类 reply 会以 member_not_following 拒绝。Message fact 携带自身的 structured mention refs,Client 只为这些 Member 渲染 mention chip。claim/done/release 和 Task change 是有序的 host-authored Activity。Active Claim 只排斥相同的 normalized Direction;不同 Direction 可以并行。Task status 从 Claims 派生,Human accept/close 是覆盖事实。Close 原子释放 active Claims 并清除 Thread Attention。Member remove 原子标记 inactive、释放 owned active Claims、清除该成员的 Attention 和 direct markers,再归档 session。Message 与 Activity facts 共用一个有界 sequence cursor。
25
+ Member reply 必须携带准确的当前 Thread revision,并在一个 operation 内更新 Message 和 Thread facts。`threadRef` 是协作主身份;released task-only Client 可为 taskful Thread 传入 Host-resolved `taskRef` alias,而 Task/Claim 操作仍以 Task ref 为准。未读工作必须先 read;revision 过期时拒绝写入。Closed Task 拒绝 reply 和新的 Attention;reopen 恢复 Task,但不恢复之前的 Attention。taskless Thread 仍支持 reply、follow、mention、Inbox、read 和 history,但没有 Claim 或 Task resolution path。顶层消息可以直接 mention Agent:被 mention 的 Member 会开始关注新 Thread。在既有 Thread 中,Human 的 reply 提到 unfollowed Agent 时必须先取得 process-local one-use confirmation token 才提交 operation;Member 的此类 reply 会以 member_not_following 拒绝。Message fact 携带自身的 structured mention refs,Client 只为这些 Member 渲染 mention chip。claim/done/release 和 Task change 是有序的 host-authored Activity。Active Claim 只排斥相同的 normalized Direction;不同 Direction 可以并行。Task status 从 Claims 派生,Human accept/close 是覆盖事实。Close 原子释放 active Claims 并清除 Thread Attention。Member remove 原子标记 inactive、释放 owned active Claims、清除该成员的 Attention 和 direct markers,再归档 session。Message 与 Activity facts 共用一个有界 sequence cursor。
26
26
 
27
27
  `changes()` 是 Client 失效通知流。每个请求声明一个可选 `scope`(workspace、channel 或 thread)和可取消的传输 signal;一次提交的 operation 只唤醒 scope 与该 operation 派生范围匹配的 waiter(成员生命周期与 presence 唤醒其 Workspace,内容操作唤醒其 Channel 与 Thread)。Thread read 会持久化提交但不派生任何 scope,因为它只推进读者自己的私有水位。每次提交后,Host 只通知 Inbox projection 可能被该 operation 改变的 Member——即 operation 的 Attention/marker delta 加上被触及 Thread 的当前关注者——而不是全部在线 Agent。
28
28
 
@@ -40,7 +40,7 @@ Team-enabled preset 在自身 Agent scope 注册五个工具,并用 `markAgent
40
40
 
41
41
  #### What the model sees
42
42
 
43
- 启用的 Member 在持久未读工作出现后,可能收到一次有界、合并的 context 通知。Structured direct mention 包含消息正文和来源;Task/Claim Activity 包含简短状态变化;普通未读只包含 Task ref、数量和 revision。通知通过 Agent 的安全 step 边界排队,正在执行的模型请求和工具不会被中断。常见路径可以直接调用 `team_thread.read`;只有需要跨 Thread 分流时才调用 `team_inbox`。
43
+ 启用的 Member 在持久未读工作出现后,可能收到一次有界、合并的 context 通知。Structured direct mention 包含消息正文和来源;Task/Claim Activity 包含简短状态变化;普通未读只包含 Thread-first 路由、数量和 revision,存在时附带 Task overlay。通知通过 Agent 的安全 step 边界排队,正在执行的模型请求和工具不会被中断。常见路径可以直接调用 `team_thread.read`;只有需要跨 Thread 分流时才调用 `team_inbox`。
44
44
 
45
45
  #### Token effect
46
46
 
@@ -17,13 +17,22 @@ export const ATTACHMENT_ORPHAN_TTL_MS = 24 * 60 * 60 * 1000;
17
17
  export function attachmentsRoot() {
18
18
  return dshHomePath('agent-team', 'attachments', 'v1');
19
19
  }
20
+ /** Absolute payload path of one stored attachment, as offered to Member agents. */
21
+ export function attachmentPayloadPath(attachmentId, name) {
22
+ return join(attachmentsRoot(), attachmentId, name);
23
+ }
20
24
  export function newAttachmentId() {
21
25
  return randomUUID();
22
26
  }
23
27
  /** Strip path separators, control characters, and leading dots from one client-supplied name. */
24
28
  export function sanitizeFileName(raw) {
25
- const cleaned = raw.replaceAll(/[\\/\u0000-\u001f\u007f]/g, '').replaceAll(/^\.+/g, '').trim();
26
- return cleaned === '' ? 'attachment' : cleaned.slice(0, 180);
29
+ // oxlint-disable-next-line no-control-regex -- strip ASCII control characters from client filenames.
30
+ const cleaned = raw.replaceAll(/[\\/\u0000-\u001f\u007f]/g, '').replaceAll(/^\.+/g, '').trim().slice(0, 180);
31
+ if (cleaned === '')
32
+ return 'attachment';
33
+ // The metadata sidecar owns 'meta.json' inside every entry directory; a
34
+ // payload with that name would be clobbered by the sidecar and unreadable.
35
+ return /^meta\.json$/i.test(cleaned) ? `_${cleaned}` : cleaned;
27
36
  }
28
37
  /** Extension-derived media types for agent-supplied files; unknown types stay generic. */
29
38
  const PATH_MEDIA_TYPES = {
@@ -97,16 +106,6 @@ export async function readAttachment(root, attachmentId) {
97
106
  const bytes = await readFile(join(dir, payload));
98
107
  return { name: meta.name, mediaType: meta.mediaType, byteSize: bytes.byteLength, uploadedAt: meta.uploadedAt, bytes };
99
108
  }
100
- /** Whether an upload with this id still has bytes on disk. */
101
- export async function attachmentExists(root, attachmentId) {
102
- try {
103
- await stat(attachmentDir(root, attachmentId));
104
- return true;
105
- }
106
- catch {
107
- return false;
108
- }
109
- }
110
109
  /** List every cache entry with its upload instant for the GC sweep. */
111
110
  export async function scanAttachmentCache(root) {
112
111
  let ids;
@@ -0,0 +1,163 @@
1
+ import { ManualCompactionError } from '@deepseek-ai/dsh-compaction';
2
+ /** Strict threshold: 200K exactly does not compact. */
3
+ export const AUTO_COMPACTION_TOKEN_LIMIT = 200_000;
4
+ /** Claim owners of a durable Human acceptance, including early-completed ones. */
5
+ export function acceptedTaskCompactionMembers(operation) {
6
+ if (operation.kind !== 'team/task-changed' || operation.actor.kind !== 'human' || operation.data.activity.kind !== 'accept')
7
+ return undefined;
8
+ return [...new Set(operation.data.claims.map(claim => claim.owner))];
9
+ }
10
+ /**
11
+ * Small Host-side accepted-Task follow-up. This is deliberately not a generic
12
+ * workflow/state-machine: a pending set deduplicates Members, an error map is
13
+ * owned by the Host, and each worker is only whenIdle → measure → compactNow.
14
+ */
15
+ export class AutoCompactionCoordinator {
16
+ options;
17
+ pending = new Set();
18
+ workers = new Map();
19
+ controller = new AbortController();
20
+ constructor(options) {
21
+ this.options = options;
22
+ }
23
+ /** Queue all unique claim owners; unavailable Members stay pending. */
24
+ schedule(memberIds) {
25
+ for (const memberId of new Set(memberIds)) {
26
+ this.pending.add(memberId);
27
+ this.start(memberId);
28
+ }
29
+ }
30
+ /** Activation/recovery calls this after a live handle becomes available. */
31
+ activated(memberId) {
32
+ this.start(memberId);
33
+ }
34
+ async dispose() {
35
+ this.controller.abort();
36
+ await Promise.all([...this.workers.values()].map(worker => worker.catch(() => { })));
37
+ this.workers.clear();
38
+ this.pending.clear();
39
+ }
40
+ start(memberId) {
41
+ if (this.controller.signal.aborted || this.workers.has(memberId) || !this.pending.has(memberId))
42
+ return;
43
+ // No live handle is not a failure: member activation/recovery calls activated().
44
+ if (this.options.agentForMember(memberId) === undefined)
45
+ return;
46
+ const worker = this.work(memberId).finally(() => { this.workers.delete(memberId); });
47
+ this.workers.set(memberId, worker);
48
+ }
49
+ async work(memberId) {
50
+ let reactivated = false;
51
+ while (!this.controller.signal.aborted && this.pending.has(memberId)) {
52
+ const agent = this.options.agentForMember(memberId);
53
+ if (agent === undefined)
54
+ return;
55
+ try {
56
+ if (!(await waitForIdleOrAbort(agent, this.controller.signal)))
57
+ return;
58
+ }
59
+ catch (error) {
60
+ this.fail(memberId, agent, `automatic compaction idle wait failed: ${describe(error)}`);
61
+ return;
62
+ }
63
+ // A restart can replace the handle while it waited; restart the turn on
64
+ // the current agent so measurement and compactNow share one scope.
65
+ if (this.options.agentForMember(memberId) !== agent)
66
+ continue;
67
+ const meter = agent.ctx.get('tokenMeter');
68
+ if (meter === undefined) {
69
+ this.fail(memberId, agent, 'automatic compaction failed: tokenMeter is unavailable in the Member scope');
70
+ return;
71
+ }
72
+ const before = meter.measure(agent.session).totalTokens;
73
+ if (before <= AUTO_COMPACTION_TOKEN_LIMIT) {
74
+ this.complete(memberId, agent);
75
+ return;
76
+ }
77
+ const engine = this.options.compactionForAgent(agent);
78
+ if (engine === undefined) {
79
+ // A roster-subtree teardown (bundle-row reload, config hot-apply)
80
+ // prunes the standing mount while this agent's scope binding still
81
+ // names the dead key, so resolution can never succeed again for this
82
+ // handle. One in-place re-activation rebuilds the binding from the
83
+ // live roster; a preset that mounts no compaction row keeps failing
84
+ // and lands in the terminal diagnostic below.
85
+ if (!reactivated && await this.tryReactivate(memberId)) {
86
+ reactivated = true;
87
+ continue;
88
+ }
89
+ this.fail(memberId, agent, 'automatic compaction failed: compaction is unavailable in the Member scope');
90
+ return;
91
+ }
92
+ try {
93
+ const result = await engine.compactNow(agent, this.controller.signal);
94
+ if (this.controller.signal.aborted)
95
+ return;
96
+ if (result === null) {
97
+ this.fail(memberId, agent, `automatic compaction returned no compactable range above ${AUTO_COMPACTION_TOKEN_LIMIT} tokens`);
98
+ return;
99
+ }
100
+ }
101
+ catch (error) {
102
+ if (this.controller.signal.aborted)
103
+ return;
104
+ if (error instanceof ManualCompactionError && error.code === 'busy')
105
+ continue;
106
+ this.fail(memberId, agent, `automatic compaction failed: ${describe(error)}`);
107
+ return;
108
+ }
109
+ const after = meter.measure(agent.session).totalTokens;
110
+ if (after >= before) {
111
+ this.fail(memberId, agent, `automatic compaction did not reduce context (${before} → ${after} tokens)`);
112
+ return;
113
+ }
114
+ this.options.cleared(memberId, agent.id);
115
+ // Still over the strict threshold: next iteration waits at a fresh idle
116
+ // boundary before attempting another compactNow.
117
+ }
118
+ }
119
+ complete(memberId, agent) {
120
+ this.pending.delete(memberId);
121
+ this.options.cleared(memberId, agent.id);
122
+ }
123
+ tryReactivate(memberId) {
124
+ return this.options.reactivate(memberId).catch(error => {
125
+ this.options.log(`automatic compaction re-activation failed: ${describe(error)} (member ${memberId})`);
126
+ return false;
127
+ });
128
+ }
129
+ fail(memberId, agent, diagnostic) {
130
+ this.pending.delete(memberId);
131
+ this.options.log(`${diagnostic} (member ${memberId}, session ${agent.id})`);
132
+ this.options.failed(memberId, agent.id, diagnostic);
133
+ }
134
+ }
135
+ /** Abortable idle wait; the Agent owns its idle promise and is never cancelled. */
136
+ function waitForIdleOrAbort(agent, signal) {
137
+ const idle = agent.whenIdle();
138
+ if (signal.aborted) {
139
+ // The agent may settle or reject after teardown; keep that late outcome
140
+ // observed without making it a coordinator failure.
141
+ void idle.catch(() => { });
142
+ return Promise.resolve(false);
143
+ }
144
+ return new Promise((resolve, reject) => {
145
+ let settled = false;
146
+ const finish = (outcome, error) => {
147
+ if (settled)
148
+ return;
149
+ settled = true;
150
+ signal.removeEventListener('abort', onAbort);
151
+ if (outcome === 'error')
152
+ reject(error);
153
+ else
154
+ resolve(outcome === 'idle');
155
+ };
156
+ const onAbort = () => { finish('aborted'); };
157
+ signal.addEventListener('abort', onAbort, { once: true });
158
+ idle.then(() => { finish('idle'); }, error => { finish('error', error); });
159
+ });
160
+ }
161
+ function describe(error) {
162
+ return error instanceof Error ? error.message : String(error);
163
+ }