@wowyuarm/dsh-agent-team 0.1.3 → 0.1.5
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 +7 -1
- package/README.zh.md +12 -3
- package/package.json +1 -1
- package/packages/agent-team/README.md +3 -3
- package/packages/agent-team/README.zh.md +3 -3
- package/packages/agent-team/lib/auto-compaction.js +39 -1
- package/packages/agent-team/lib/index.js +123 -14
- package/packages/agent-team/lib/ledger.js +355 -110
- package/packages/agent-team/lib/spec.js +39 -11
- package/packages/agent-team/lib/typert.host.js +257 -86
- package/packages/agent-team/lib/typert.remote-client.d.ts +5 -1
- package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
- package/packages/agent-team/lib/typert.remote-client.js +196 -59
- package/packages/agent-team/lib/types/auto-compaction.d.ts +9 -0
- package/packages/agent-team/lib/types/auto-compaction.d.ts.map +1 -1
- package/packages/agent-team/lib/types/index.d.ts +24 -7
- package/packages/agent-team/lib/types/index.d.ts.map +1 -1
- package/packages/agent-team/lib/types/ledger.d.ts +36 -2
- package/packages/agent-team/lib/types/ledger.d.ts.map +1 -1
- package/packages/agent-team/lib/types/spec.d.ts.map +1 -1
- package/packages/agent-team/lib/types/types.d.ts +109 -30
- package/packages/agent-team/lib/types/types.d.ts.map +1 -1
- package/packages/agent-team/preset/team-member/agent.cordis.yml +1 -1
- package/packages/client-agent-team/README.md +2 -1
- package/packages/client-agent-team/README.zh.md +2 -1
- package/packages/client-agent-team/lib/client.js +1082 -380
- package/packages/client-agent-team/lib/client.js.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts +2 -1
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.js +73 -15
- package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamChannelPage.js +34 -10
- package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.js +3 -1
- package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts +6 -1
- package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamComposer.js +76 -20
- package/packages/client-agent-team/lib/types/client/TeamConversation.d.ts +1 -1
- package/packages/client-agent-team/lib/types/client/TeamConversation.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamConversation.js +3 -3
- package/packages/client-agent-team/lib/types/client/TeamMemberSessionComposer.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamMemberSessionComposer.js +16 -1
- package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts +4 -1
- package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamMessage.js +62 -25
- package/packages/client-agent-team/lib/types/client/TeamRowMenu.d.ts +3 -2
- package/packages/client-agent-team/lib/types/client/TeamRowMenu.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamSidebarSection.d.ts +5 -2
- package/packages/client-agent-team/lib/types/client/TeamSidebarSection.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamSidebarSection.js +2 -4
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts +2 -1
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.js +92 -18
- package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts +1 -1
- package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.js +5 -2
- package/packages/client-agent-team/lib/types/client/index.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/index.js +16 -13
- package/packages/client-agent-team/lib/types/client/locales.d.ts +40 -0
- package/packages/client-agent-team/lib/types/client/locales.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/locales.js +40 -0
- package/packages/client-agent-team/lib/types/client/navigation.d.ts +1 -1
- package/packages/client-agent-team/lib/types/client/navigation.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/navigation.js +12 -6
- package/packages/client-agent-team/lib/types/client/sidebar-sections.d.ts +16 -0
- package/packages/client-agent-team/lib/types/client/sidebar-sections.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/sidebar-sections.js +102 -0
- package/packages/client-agent-team/lib/types/client/slots.d.ts +3 -1
- package/packages/client-agent-team/lib/types/client/slots.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/task-refs.d.ts +1 -1
- package/packages/client-agent-team/lib/types/client/task-refs.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/task-refs.js +1 -1
- package/packages/client-agent-team/lib/types/client/team-formatters.d.ts +9 -0
- package/packages/client-agent-team/lib/types/client/team-formatters.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/team-formatters.js +22 -2
- package/packages/tool-agent-team/README.md +7 -7
- package/packages/tool-agent-team/README.zh.md +7 -7
- package/packages/tool-agent-team/lib/index.js +38 -28
- package/packages/tool-agent-team/lib/types/index.d.ts.map +1 -1
package/README.md
CHANGED
|
@@ -13,7 +13,13 @@ An opt-in plugin for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-
|
|
|
13
13
|
|
|
14
14
|
## Preview
|
|
15
15
|
|
|
16
|
-

|
|
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
|
+

|
|
17
23
|
|
|
18
24
|
## Quick start
|
|
19
25
|
|
package/README.zh.md
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
[English](README.md) | 简体中文
|
|
4
4
|
|
|
5
|
+
[](https://www.npmjs.com/package/@wowyuarm/dsh-agent-team)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
[](https://github.com/wowyuarm/dsh-agent-team/releases)
|
|
5
8
|
[](https://awesome-dsh-plugin.com/p/wowyuarm/dsh-agent-team/)
|
|
6
9
|
|
|
7
10
|
**dsh-agent-team** 帮助 human 有序管理任务、把 agents 用成真正的协作者:Agent 是 session 的持久身份;Workspace 按项目组织 agents 与 sessions;Channel 承载职责分派;Task Thread 把多个 session agent 串成一条推进线。
|
|
@@ -10,7 +13,13 @@
|
|
|
10
13
|
|
|
11
14
|
## 预览
|
|
12
15
|
|
|
13
|
-

|
|
17
|
+
|
|
18
|
+
### Task Thread
|
|
19
|
+
|
|
20
|
+
Task Thread 把 Claim、Agent 交接、Human 验收和后续回复保留在同一条可持续阅读的上下文中。
|
|
21
|
+
|
|
22
|
+

|
|
14
23
|
|
|
15
24
|
## 快速开始
|
|
16
25
|
|
|
@@ -92,7 +101,7 @@ dsh web
|
|
|
92
101
|
|
|
93
102
|
## 开发
|
|
94
103
|
|
|
95
|
-
维护中的文档入口是 [`docs/README.md`](docs/README.md)。常用检查命令:
|
|
104
|
+
维护中的文档入口是 [`docs/README.zh.md`](docs/README.zh.md)。常用检查命令:
|
|
96
105
|
|
|
97
106
|
```sh
|
|
98
107
|
corepack pnpm install
|
|
@@ -106,7 +115,7 @@ npm pack --dry-run
|
|
|
106
115
|
|
|
107
116
|
`npm run test:browser` 使用相邻的 `../deepseek-harness` checkout、隔离临时 profile 和 `/usr/bin/google-chrome`(可用 `CHROME_PATH` 覆盖),不需要 provider credentials。手动检查时,`npm run preview:ui` 会加载不调用模型的 Team fixture;`DEEPSEEK_API_KEY=... npm run preview` 会启动真实 provider preview。两个 preview 命令都会在 `Ctrl+C` 后清理临时状态。
|
|
108
117
|
|
|
109
|
-
架构和协作协议见 [`docs/architecture.md`](docs/architecture.md) 与 [`docs/team-collaboration.md`](docs/team-collaboration.md)。
|
|
118
|
+
架构和协作协议见 [`docs/architecture.zh.md`](docs/architecture.zh.md) 与 [`docs/team-collaboration.zh.md`](docs/team-collaboration.zh.md)。
|
|
110
119
|
|
|
111
120
|
## 许可证
|
|
112
121
|
|
package/package.json
CHANGED
|
@@ -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
|
|
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
|
|
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。创建
|
|
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
|
|
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 包含简短状态变化;普通未读只包含
|
|
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
|
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import { ManualCompactionError } from '@deepseek-ai/dsh-compaction';
|
|
2
2
|
/** Strict threshold: 200K exactly does not compact. */
|
|
3
3
|
export const AUTO_COMPACTION_TOKEN_LIMIT = 200_000;
|
|
4
|
+
/**
|
|
5
|
+
* Suggested summary marker of the pre-compaction memory hint. Same shape as
|
|
6
|
+
* the inbox and recovery notices so future turns can recognize it.
|
|
7
|
+
*/
|
|
8
|
+
export const PRE_COMPACTION_NOTICE_SUMMARY = 'Compaction is imminent; consider persisting key conclusions.';
|
|
9
|
+
/** Suggested pre-compaction hint body; writing is the Agent's own call. */
|
|
10
|
+
export function preCompactionNoticeText() {
|
|
11
|
+
return 'This session is about to be compacted, which will summarize away the current conversation detail. If there are durable conclusions worth keeping — validated facts, decisions, or reusable details — consider writing them to your memory or notes now. This is only a reminder: decide yourself whether anything is worth persisting, and do nothing if not. Do not start new work because of this message.';
|
|
12
|
+
}
|
|
4
13
|
/** Claim owners of a durable Human acceptance, including early-completed ones. */
|
|
5
14
|
export function acceptedTaskCompactionMembers(operation) {
|
|
6
15
|
if (operation.kind !== 'team/task-changed' || operation.actor.kind !== 'human' || operation.data.activity.kind !== 'accept')
|
|
@@ -69,11 +78,40 @@ export class AutoCompactionCoordinator {
|
|
|
69
78
|
this.fail(memberId, agent, 'automatic compaction failed: tokenMeter is unavailable in the Member scope');
|
|
70
79
|
return;
|
|
71
80
|
}
|
|
72
|
-
|
|
81
|
+
let before = meter.measure(agent.session).totalTokens;
|
|
73
82
|
if (before <= AUTO_COMPACTION_TOKEN_LIMIT) {
|
|
74
83
|
this.complete(memberId, agent);
|
|
75
84
|
return;
|
|
76
85
|
}
|
|
86
|
+
// The session is over the threshold and about to be compacted: steer one
|
|
87
|
+
// advisory hint so the Agent can persist its own key conclusions first.
|
|
88
|
+
// The hint turn itself is ordinary agent work — its failure is contained
|
|
89
|
+
// by the agent loop and never blocks compaction; only the fresh idle
|
|
90
|
+
// boundary is awaited before the final measurement and compactNow.
|
|
91
|
+
if (this.options.steerPreCompaction !== undefined) {
|
|
92
|
+
try {
|
|
93
|
+
this.options.steerPreCompaction(agent);
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
this.options.log(`pre-compaction memory hint failed: ${describe(error)} (member ${memberId}, session ${agent.id})`);
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
if (!(await waitForIdleOrAbort(agent, this.controller.signal)))
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
this.options.log(`pre-compaction hint idle wait failed: ${describe(error)} (member ${memberId}, session ${agent.id})`);
|
|
104
|
+
}
|
|
105
|
+
if (this.controller.signal.aborted)
|
|
106
|
+
return;
|
|
107
|
+
if (this.options.agentForMember(memberId) !== agent)
|
|
108
|
+
continue;
|
|
109
|
+
before = meter.measure(agent.session).totalTokens;
|
|
110
|
+
if (before <= AUTO_COMPACTION_TOKEN_LIMIT) {
|
|
111
|
+
this.complete(memberId, agent);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
77
115
|
const engine = this.options.compactionForAgent(agent);
|
|
78
116
|
if (engine === undefined) {
|
|
79
117
|
// A roster-subtree teardown (bundle-row reload, config hot-apply)
|
|
@@ -52,7 +52,7 @@ import { SessionId } from '@deepseek-ai/dsh-session';
|
|
|
52
52
|
import { effectiveSandboxMode, setSandboxMode } from '@deepseek-ai/dsh-sandbox-policy';
|
|
53
53
|
import { Remote, TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
|
|
54
54
|
import { ATTACHMENT_MAX_BYTES, attachmentPayloadPath, attachmentsRoot, copyPathAttachment, newAttachmentId, readAttachment, sanitizeMediaType, sweepAttachmentCache, validatePathAttachment, writeAttachment } from "./attachments.js";
|
|
55
|
-
import { acceptedTaskCompactionMembers, AutoCompactionCoordinator } from "./auto-compaction.js";
|
|
55
|
+
import { acceptedTaskCompactionMembers, AutoCompactionCoordinator, PRE_COMPACTION_NOTICE_SUMMARY, preCompactionNoticeText } from "./auto-compaction.js";
|
|
56
56
|
import { AGENT_TEAM_HUMAN_MEMBER_ID, AgentTeamLedger, agentTeamHumanActor } from "./ledger.js";
|
|
57
57
|
import { classifyRecoverableError, RecoveryCoordinator, RECOVERY_MAX_CONSECUTIVE_ERRORS } from "./recovery.js";
|
|
58
58
|
import { agentTeamDomainSpec } from "./spec.js";
|
|
@@ -96,8 +96,10 @@ let AgentTeam = (() => {
|
|
|
96
96
|
let _updateChannel_decorators;
|
|
97
97
|
let _addMember_decorators;
|
|
98
98
|
let _recoverMember_decorators;
|
|
99
|
+
let _clearMemberContext_decorators;
|
|
99
100
|
let _updateMember_decorators;
|
|
100
101
|
let _changeTask_decorators;
|
|
102
|
+
let _promoteThread_decorators;
|
|
101
103
|
let _joinChannel_decorators;
|
|
102
104
|
let _removeChannelMember_decorators;
|
|
103
105
|
let _sendMessage_decorators;
|
|
@@ -120,8 +122,10 @@ let AgentTeam = (() => {
|
|
|
120
122
|
_updateChannel_decorators = [Remote('updateChannel')];
|
|
121
123
|
_addMember_decorators = [Remote('addMember')];
|
|
122
124
|
_recoverMember_decorators = [Remote('recoverMember')];
|
|
125
|
+
_clearMemberContext_decorators = [Remote('clearMemberContext')];
|
|
123
126
|
_updateMember_decorators = [Remote('updateMember')];
|
|
124
127
|
_changeTask_decorators = [Remote('changeTask')];
|
|
128
|
+
_promoteThread_decorators = [Remote('promoteThread')];
|
|
125
129
|
_joinChannel_decorators = [Remote('joinChannel')];
|
|
126
130
|
_removeChannelMember_decorators = [Remote('removeChannelMember')];
|
|
127
131
|
_sendMessage_decorators = [Remote('sendMessage')];
|
|
@@ -141,8 +145,10 @@ let AgentTeam = (() => {
|
|
|
141
145
|
__esDecorate(this, null, _updateChannel_decorators, { kind: "method", name: "updateChannel", static: false, private: false, access: { has: obj => "updateChannel" in obj, get: obj => obj.updateChannel }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
142
146
|
__esDecorate(this, null, _addMember_decorators, { kind: "method", name: "addMember", static: false, private: false, access: { has: obj => "addMember" in obj, get: obj => obj.addMember }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
143
147
|
__esDecorate(this, null, _recoverMember_decorators, { kind: "method", name: "recoverMember", static: false, private: false, access: { has: obj => "recoverMember" in obj, get: obj => obj.recoverMember }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
148
|
+
__esDecorate(this, null, _clearMemberContext_decorators, { kind: "method", name: "clearMemberContext", static: false, private: false, access: { has: obj => "clearMemberContext" in obj, get: obj => obj.clearMemberContext }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
144
149
|
__esDecorate(this, null, _updateMember_decorators, { kind: "method", name: "updateMember", static: false, private: false, access: { has: obj => "updateMember" in obj, get: obj => obj.updateMember }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
145
150
|
__esDecorate(this, null, _changeTask_decorators, { kind: "method", name: "changeTask", static: false, private: false, access: { has: obj => "changeTask" in obj, get: obj => obj.changeTask }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
151
|
+
__esDecorate(this, null, _promoteThread_decorators, { kind: "method", name: "promoteThread", static: false, private: false, access: { has: obj => "promoteThread" in obj, get: obj => obj.promoteThread }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
146
152
|
__esDecorate(this, null, _joinChannel_decorators, { kind: "method", name: "joinChannel", static: false, private: false, access: { has: obj => "joinChannel" in obj, get: obj => obj.joinChannel }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
147
153
|
__esDecorate(this, null, _removeChannelMember_decorators, { kind: "method", name: "removeChannelMember", static: false, private: false, access: { has: obj => "removeChannelMember" in obj, get: obj => obj.removeChannelMember }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
148
154
|
__esDecorate(this, null, _sendMessage_decorators, { kind: "method", name: "sendMessage", static: false, private: false, access: { has: obj => "sendMessage" in obj, get: obj => obj.sendMessage }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
@@ -213,6 +219,14 @@ let AgentTeam = (() => {
|
|
|
213
219
|
this.emitAutoCompactionChanged(memberId);
|
|
214
220
|
},
|
|
215
221
|
log: message => { this.ctx.logger.warn(`agent-team: ${message}`); },
|
|
222
|
+
// Advisory only: the Agent decides whether anything is worth persisting.
|
|
223
|
+
steerPreCompaction: agent => {
|
|
224
|
+
const hint = createUserMessage({
|
|
225
|
+
content: [{ type: 'text', text: preCompactionNoticeText() }],
|
|
226
|
+
source: { kind: 'plugin', plugin: AGENT_TEAM_PLUGIN_ID, form: 'notice', summary: PRE_COMPACTION_NOTICE_SUMMARY },
|
|
227
|
+
});
|
|
228
|
+
agent.steer(hint);
|
|
229
|
+
},
|
|
216
230
|
});
|
|
217
231
|
}
|
|
218
232
|
/** Open the durable ledger and restore every enabled Member independently. */
|
|
@@ -243,8 +257,15 @@ let AgentTeam = (() => {
|
|
|
243
257
|
this.notifyMember(agent);
|
|
244
258
|
}
|
|
245
259
|
// A turn that ends without an error closes any automatic recovery episode.
|
|
246
|
-
|
|
247
|
-
|
|
260
|
+
// The idle transition is itself presence-affecting (working → available),
|
|
261
|
+
// so it must wake workspace watchers exactly like the running transition
|
|
262
|
+
// above; without this wake, cached Client member rows keep showing the
|
|
263
|
+
// Member as working after every turn until an unrelated change arrives.
|
|
264
|
+
if (status === 'idle' && member !== undefined) {
|
|
265
|
+
if (this.memberFailures.get(member.memberId)?.runtime === undefined) {
|
|
266
|
+
this.recovery.onCleanTurnEnd(member.memberId);
|
|
267
|
+
}
|
|
268
|
+
this.emitChanged([{ kind: 'workspace', workspaceId: member.workspaceId }]);
|
|
248
269
|
}
|
|
249
270
|
});
|
|
250
271
|
const domain = await this.ctx.storageDomain.open(agentTeamDomainSpec);
|
|
@@ -426,9 +447,11 @@ let AgentTeam = (() => {
|
|
|
426
447
|
});
|
|
427
448
|
}
|
|
428
449
|
/**
|
|
429
|
-
* Operator nudge for
|
|
430
|
-
* into its live session
|
|
431
|
-
*
|
|
450
|
+
* Operator nudge for a Member that stopped making progress: steer a
|
|
451
|
+
* continuation prompt into its live session, rebuild it after an orphaned
|
|
452
|
+
* preset composition, or re-run activation when no live session exists.
|
|
453
|
+
* Runtime-only — no ledger operation, no suspend. Taking over manually also
|
|
454
|
+
* cancels any pending automatic recovery episode.
|
|
432
455
|
*/
|
|
433
456
|
async recoverMember(request) {
|
|
434
457
|
this.requireAccepting();
|
|
@@ -444,10 +467,80 @@ let AgentTeam = (() => {
|
|
|
444
467
|
await this.reactivateMember(request.memberId);
|
|
445
468
|
return Object.freeze({ status: this.memberStatus(member) });
|
|
446
469
|
}
|
|
470
|
+
// A failed activation also leaves nothing to steer; re-running it is the
|
|
471
|
+
// only way back. A renewed failure stays non-throwing: the refreshed
|
|
472
|
+
// status carries the activation diagnostic for the sidebar.
|
|
473
|
+
if (handle === undefined) {
|
|
474
|
+
if (member.state !== 'enabled')
|
|
475
|
+
throw new Error(`Agent Member '${member.handle}' is ${member.state}; only enabled Members can be restarted`);
|
|
476
|
+
this.ctx.logger.info(`agent-team: restarting member '${member.handle}' after a failed activation`);
|
|
477
|
+
await this.reactivateMember(request.memberId);
|
|
478
|
+
return Object.freeze({ status: this.memberStatus(member) });
|
|
479
|
+
}
|
|
447
480
|
this.ctx.logger.info(`agent-team: operator asked member '${member.handle}' to resume`);
|
|
448
481
|
this.steerResume(member, this.manualResumeText());
|
|
449
482
|
return Object.freeze({ status: this.memberStatus(member) });
|
|
450
483
|
}
|
|
484
|
+
/**
|
|
485
|
+
* Start one enabled Member from a new context: dispose the live handle,
|
|
486
|
+
* archive the previous Session (its log stays on disk for history), and
|
|
487
|
+
* activate a fresh Session under a new sessionId, so preset, tools, private
|
|
488
|
+
* memory, and model selection all reload while the next turn carries no
|
|
489
|
+
* history. The durable operation moves the Member's sessionId; identity,
|
|
490
|
+
* memory path, and binding survive. A new id is what keeps the Web Client
|
|
491
|
+
* seat live: a disposed generation's resident instance keeps its `removed`
|
|
492
|
+
* bit forever, so renewing under the same id would leave a permanently
|
|
493
|
+
* grayed session view.
|
|
494
|
+
*/
|
|
495
|
+
async clearMemberContext(request) {
|
|
496
|
+
return this.enqueueLifecycle(async () => {
|
|
497
|
+
this.requireAccepting();
|
|
498
|
+
this.requireWorkspace(request.workspaceId);
|
|
499
|
+
const stored = this.requireLedger().getMember(request.memberId);
|
|
500
|
+
if (stored === undefined || stored.workspaceId !== request.workspaceId)
|
|
501
|
+
throw new Error(`unknown Member '${request.memberId}' in workspace '${request.workspaceId}'`);
|
|
502
|
+
if (stored.state !== 'enabled')
|
|
503
|
+
throw new Error(`Agent Member '${stored.handle}' is ${stored.state}; only enabled Members can start from a new context`);
|
|
504
|
+
const active = this.handles.get(request.memberId);
|
|
505
|
+
if (active === undefined)
|
|
506
|
+
throw new Error(`Agent Member '${stored.handle}' has no active session to clear`);
|
|
507
|
+
if (this.runningAgents.has(active.agent.id))
|
|
508
|
+
throw new Error(`Agent Member '${stored.handle}' is still running; wait for the current turn to end before starting from a new context`);
|
|
509
|
+
const previousSessionId = stored.sessionId;
|
|
510
|
+
// The fresh id derives from the requestId, so a retried identical
|
|
511
|
+
// request mints the same id and the ledger dedupes it instead of
|
|
512
|
+
// colliding; the format matches addMember's `agent-team-<uuid>`.
|
|
513
|
+
const sessionId = SessionId(`agent-team-${request.requestId}`);
|
|
514
|
+
const result = await this.requireLedger().renewMemberSession({ ...request, sessionId, actor: agentTeamHumanActor() });
|
|
515
|
+
if (result.committed)
|
|
516
|
+
this.emitCommitted(result.value.receipt);
|
|
517
|
+
else {
|
|
518
|
+
// A retried identical request already renewed this Member; report the
|
|
519
|
+
// recorded outcome without another dispose/reactivate cycle.
|
|
520
|
+
return Object.freeze({ receipt: result.value.receipt, status: this.memberStatus(result.value.member) });
|
|
521
|
+
}
|
|
522
|
+
const renewed = result.value.member;
|
|
523
|
+
// Drop the old handle's transient state: pending recovery episodes and
|
|
524
|
+
// error markers belong to the disposed agent, not to the Member.
|
|
525
|
+
this.recovery.stopTracking(request.memberId);
|
|
526
|
+
await active.dispose();
|
|
527
|
+
this.handles.delete(request.memberId);
|
|
528
|
+
this.modelSelections.delete(request.memberId);
|
|
529
|
+
this.clearMemberFailure(request.memberId, 'activation');
|
|
530
|
+
this.clearMemberNotificationState(request.memberId);
|
|
531
|
+
// The previous log survives on disk; archiving hides it from every
|
|
532
|
+
// grouping surface so one Member keeps exactly one visible Session.
|
|
533
|
+
await this.ctx.workspaceRegistry.archiveSession(previousSessionId);
|
|
534
|
+
await this.activateMember(renewed, undefined, undefined, previousSessionId);
|
|
535
|
+
const reactivated = this.handles.get(request.memberId);
|
|
536
|
+
if (reactivated === undefined) {
|
|
537
|
+
// Reactivation failed; the activation diagnostic carries the reason and
|
|
538
|
+
// the durable renewal stays honest about the attempt.
|
|
539
|
+
throw new Error(`Agent Member '${stored.handle}' failed to start a new context: ${this.memberFailures.get(request.memberId)?.activation ?? 'unknown error'}`);
|
|
540
|
+
}
|
|
541
|
+
return Object.freeze({ receipt: result.value.receipt, status: this.memberStatus(renewed) });
|
|
542
|
+
});
|
|
543
|
+
}
|
|
451
544
|
/** Ledger handle for log lines; falls back to the raw id when unknown. */
|
|
452
545
|
memberLabel(memberId) {
|
|
453
546
|
return this.ledger?.getMember(memberId)?.handle ?? memberId;
|
|
@@ -545,6 +638,14 @@ let AgentTeam = (() => {
|
|
|
545
638
|
this.emitCommittedOutcome(result);
|
|
546
639
|
return result.value;
|
|
547
640
|
}
|
|
641
|
+
/** Human-only promotion of a taskless Thread into a real Task plus public Message. */
|
|
642
|
+
async promoteThread(request) {
|
|
643
|
+
this.requireAccepting();
|
|
644
|
+
this.requireWorkspace(request.workspaceId);
|
|
645
|
+
const result = await this.requireLedger().promoteThread({ ...request, actor: agentTeamHumanActor() });
|
|
646
|
+
this.emitCommittedOutcome(result);
|
|
647
|
+
return result.value;
|
|
648
|
+
}
|
|
548
649
|
/** Human-only Channel membership grant; it never injects historical Thread bodies. */
|
|
549
650
|
async joinChannel(request) {
|
|
550
651
|
const actor = this.humanCall(request.workspaceId);
|
|
@@ -564,7 +665,7 @@ let AgentTeam = (() => {
|
|
|
564
665
|
this.emitCommitted(result.value.receipt);
|
|
565
666
|
return result.value;
|
|
566
667
|
}
|
|
567
|
-
/** Human top-level
|
|
668
|
+
/** Human top-level Thread start; asTask attaches an optional Task overlay. */
|
|
568
669
|
async sendMessage(request) {
|
|
569
670
|
return this.sendMessageAs(this.humanCall(request.workspaceId), request);
|
|
570
671
|
}
|
|
@@ -667,7 +768,7 @@ let AgentTeam = (() => {
|
|
|
667
768
|
this.requireWorkspace(request.workspaceId);
|
|
668
769
|
return this.requireLedger().view(request);
|
|
669
770
|
}
|
|
670
|
-
/** Agent-only top-level
|
|
771
|
+
/** Agent-only top-level Thread start. Workspace identity is verified against the live binding. */
|
|
671
772
|
async sendMessageForAgent(agent, request) {
|
|
672
773
|
return this.sendMessageAs(this.memberCall(agent, request.workspaceId), request);
|
|
673
774
|
}
|
|
@@ -808,7 +909,7 @@ let AgentTeam = (() => {
|
|
|
808
909
|
throw error;
|
|
809
910
|
}
|
|
810
911
|
}
|
|
811
|
-
async activateMember(member, knownWorkspacePath, knownSessions) {
|
|
912
|
+
async activateMember(member, knownWorkspacePath, knownSessions, forkedFrom) {
|
|
812
913
|
if (this.handles.has(member.memberId))
|
|
813
914
|
return;
|
|
814
915
|
let created;
|
|
@@ -842,7 +943,9 @@ let AgentTeam = (() => {
|
|
|
842
943
|
? await this.ctx.agents.resume({ resumeSessionId: member.sessionId, agentOptions, setup })
|
|
843
944
|
: await this.ctx.agents.create({
|
|
844
945
|
sessionId: member.sessionId,
|
|
845
|
-
|
|
946
|
+
// A context renewal records its fork lineage so the archived
|
|
947
|
+
// previous Session stays discoverable from the durable header.
|
|
948
|
+
meta: { cwd: workspacePath, agentPreset: member.presetId, ...(forkedFrom === undefined ? {} : { parentSession: forkedFrom }) },
|
|
846
949
|
agentOptions,
|
|
847
950
|
setup,
|
|
848
951
|
});
|
|
@@ -1086,7 +1189,8 @@ let AgentTeam = (() => {
|
|
|
1086
1189
|
const sender = fact.message.sender === AGENT_TEAM_HUMAN_MEMBER_ID
|
|
1087
1190
|
? 'human' : this.requireLedger().getMember(fact.message.sender)?.handle ?? fact.message.sender;
|
|
1088
1191
|
const detail = ['Direct Team mention', `From: ${sender}`, `Channel: ${item.channelRef}`,
|
|
1089
|
-
|
|
1192
|
+
...(item.task === undefined ? [] : [`Task: ${item.task.taskRef}`]),
|
|
1193
|
+
`Thread: ${item.thread.threadRef}`, `Message ref: ${fact.message.messageRef}`,
|
|
1090
1194
|
`Message: ${this.boundedNotificationBody(fact.message.body)}`].join('\n');
|
|
1091
1195
|
if (append(detail))
|
|
1092
1196
|
detailedFactCount += 1;
|
|
@@ -1098,12 +1202,14 @@ let AgentTeam = (() => {
|
|
|
1098
1202
|
}
|
|
1099
1203
|
}
|
|
1100
1204
|
const ordinaryCount = facts.filter(entry => entry.fact.kind === 'message' && !entry.direct).length;
|
|
1101
|
-
if (ordinaryCount > 0)
|
|
1102
|
-
|
|
1205
|
+
if (ordinaryCount > 0) {
|
|
1206
|
+
const route = item.task === undefined ? `Thread ${item.thread.threadRef}` : `Task ${item.task.taskRef}`;
|
|
1207
|
+
append(`${route}: ${ordinaryCount} unread update${ordinaryCount === 1 ? '' : 's'}.`);
|
|
1208
|
+
}
|
|
1103
1209
|
}
|
|
1104
1210
|
if (omitted)
|
|
1105
1211
|
sections.push('More unread work remains in team_inbox; the automatic context is bounded.');
|
|
1106
|
-
sections.push('Use team_thread read with the relevant
|
|
1212
|
+
sections.push('Use team_thread read with the relevant threadRef before acting or replying. Use team_inbox only when you need to triage the remaining Threads.');
|
|
1107
1213
|
return sections.join('\n\n');
|
|
1108
1214
|
}
|
|
1109
1215
|
boundedNotificationBody(body) {
|
|
@@ -1125,6 +1231,9 @@ let AgentTeam = (() => {
|
|
|
1125
1231
|
if (activity.kind === 'claims_released') {
|
|
1126
1232
|
return `Team Task update\n${actor}'s Claims ${activity.claimRefs.join(', ')} were released on Task ${activity.taskRef}.`;
|
|
1127
1233
|
}
|
|
1234
|
+
if (activity.kind === 'promote') {
|
|
1235
|
+
return `Team Task update\n${actor} created Task ${activity.taskRef} from Thread ${activity.threadRef}; it is open for claims.`;
|
|
1236
|
+
}
|
|
1128
1237
|
const released = 'releasedClaimRefs' in activity && activity.releasedClaimRefs !== undefined && activity.releasedClaimRefs.length > 0
|
|
1129
1238
|
? ` Released Claims: ${activity.releasedClaimRefs.join(', ')}.` : '';
|
|
1130
1239
|
return `Team Task update\n${actor} ${activity.kind} Task ${activity.taskRef}.${released}`;
|