@wowyuarm/dsh-agent-team 0.1.0 → 0.1.2
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 +14 -2
- package/README.zh.md +10 -2
- package/package.json +32 -24
- package/packages/agent-team/lib/attachments.js +157 -0
- package/packages/agent-team/lib/index.js +280 -22
- package/packages/agent-team/lib/ledger.js +98 -22
- package/packages/agent-team/lib/recovery.js +87 -0
- package/packages/agent-team/lib/spec.js +27 -5
- package/packages/agent-team/lib/typert.host.js +313 -26
- package/packages/agent-team/lib/typert.remote-client.d.ts +9 -1
- package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
- package/packages/agent-team/lib/typert.remote-client.js +234 -17
- package/packages/agent-team/lib/types/attachments.d.ts +69 -0
- package/packages/agent-team/lib/types/attachments.d.ts.map +1 -0
- package/packages/agent-team/lib/types/index.d.ts +68 -6
- package/packages/agent-team/lib/types/index.d.ts.map +1 -1
- package/packages/agent-team/lib/types/ledger.d.ts +17 -1
- package/packages/agent-team/lib/types/ledger.d.ts.map +1 -1
- package/packages/agent-team/lib/types/recovery.d.ts +42 -0
- package/packages/agent-team/lib/types/recovery.d.ts.map +1 -0
- package/packages/agent-team/lib/types/spec.d.ts.map +1 -1
- package/packages/agent-team/lib/types/types.d.ts +94 -3
- package/packages/agent-team/lib/types/types.d.ts.map +1 -1
- package/packages/agent-team/preset/team-member/agent.cordis.yml +2 -2
- package/packages/client-agent-team/README.md +4 -3
- package/packages/client-agent-team/README.zh.md +4 -3
- package/packages/client-agent-team/lib/client.js +1791 -322
- package/packages/client-agent-team/lib/client.js.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts +5 -2
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.js +108 -59
- package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts +7 -1
- package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamChannelPage.js +80 -19
- package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.js +38 -19
- package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts +4 -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 +61 -4
- 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/TeamMessage.d.ts +12 -2
- package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamMessage.js +152 -14
- package/packages/client-agent-team/lib/types/client/TeamRunDivider.d.ts +10 -0
- package/packages/client-agent-team/lib/types/client/TeamRunDivider.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/TeamRunDivider.js +12 -0
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts +7 -0
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.js +93 -16
- 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 +6 -5
- package/packages/client-agent-team/lib/types/client/attachment-preview.d.ts +22 -0
- package/packages/client-agent-team/lib/types/client/attachment-preview.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/attachment-preview.js +35 -0
- package/packages/client-agent-team/lib/types/client/drafts.d.ts +41 -0
- package/packages/client-agent-team/lib/types/client/drafts.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/drafts.js +144 -0
- package/packages/client-agent-team/lib/types/client/index.d.ts +2 -0
- package/packages/client-agent-team/lib/types/client/index.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/index.js +50 -10
- package/packages/client-agent-team/lib/types/client/locales.d.ts +34 -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 +34 -0
- package/packages/client-agent-team/lib/types/client/multi-menu-field.d.ts +33 -0
- package/packages/client-agent-team/lib/types/client/multi-menu-field.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/multi-menu-field.js +30 -0
- package/packages/client-agent-team/lib/types/client/navigation.d.ts +16 -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 +45 -8
- package/packages/client-agent-team/lib/types/client/sidebar-drag.d.ts +35 -0
- package/packages/client-agent-team/lib/types/client/sidebar-drag.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/sidebar-drag.js +70 -0
- package/packages/client-agent-team/lib/types/client/sidebar-order.d.ts +27 -0
- package/packages/client-agent-team/lib/types/client/sidebar-order.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/sidebar-order.js +170 -0
- package/packages/client-agent-team/lib/types/client/slots.d.ts +18 -2
- 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 +20 -0
- package/packages/client-agent-team/lib/types/client/task-refs.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/task-refs.js +58 -0
- package/packages/client-agent-team/lib/types/client/team-formatters.d.ts +15 -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 +30 -2
- package/packages/client-agent-team/lib/types/client/team-separators.d.ts +8 -0
- package/packages/client-agent-team/lib/types/client/team-separators.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/team-separators.js +14 -0
- package/packages/tool-agent-team/lib/index.js +10 -6
- package/packages/tool-agent-team/lib/types/index.d.ts.map +1 -1
|
@@ -44,13 +44,16 @@ import { mkdir, rm, writeFile } from 'node:fs/promises';
|
|
|
44
44
|
import { join } from 'node:path';
|
|
45
45
|
import { isDeepStrictEqual } from 'node:util';
|
|
46
46
|
import { Service } from '@deepseek-ai/cordis';
|
|
47
|
+
import { installModelSelection } from '@deepseek-ai/dsh-agent';
|
|
47
48
|
import { createUserMessage } from '@deepseek-ai/dsh-llm';
|
|
48
49
|
import { dshHomePath } from '@deepseek-ai/dsh-home-paths';
|
|
49
50
|
import { scopeOf } from '@deepseek-ai/dsh-scope';
|
|
50
51
|
import { SessionId } from '@deepseek-ai/dsh-session';
|
|
51
52
|
import { effectiveSandboxMode, setSandboxMode } from '@deepseek-ai/dsh-sandbox-policy';
|
|
52
53
|
import { Remote, TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
|
|
54
|
+
import { ATTACHMENT_MAX_BYTES, attachmentsRoot, copyPathAttachment, newAttachmentId, readAttachment, sanitizeMediaType, sweepAttachmentCache, validatePathAttachment, writeAttachment } from "./attachments.js";
|
|
53
55
|
import { AGENT_TEAM_HUMAN_MEMBER_ID, AgentTeamLedger, agentTeamHumanActor } from "./ledger.js";
|
|
56
|
+
import { classifyRecoverableError, RecoveryCoordinator, RECOVERY_MAX_ATTEMPTS } from "./recovery.js";
|
|
54
57
|
import { agentTeamDomainSpec } from "./spec.js";
|
|
55
58
|
export { agentTeamDomainSpec, agentTeamOperationSchema } from "./spec.js";
|
|
56
59
|
export { AGENT_TEAM_HUMAN_MEMBER_ID, AGENT_TEAM_INITIALIZE_REQUEST_ID } from "./ledger.js";
|
|
@@ -81,16 +84,20 @@ export const AGENT_TEAM_TOOL_NAMES = Object.freeze([
|
|
|
81
84
|
let AgentTeam = (() => {
|
|
82
85
|
let _classSuper = TypertRemoteService;
|
|
83
86
|
let _instanceExtraInitializers = [];
|
|
87
|
+
let _resolveTaskRefs_decorators;
|
|
84
88
|
let _membersForClient_decorators;
|
|
85
89
|
let _changes_decorators;
|
|
86
90
|
let _createChannel_decorators;
|
|
87
91
|
let _updateChannel_decorators;
|
|
88
92
|
let _addMember_decorators;
|
|
93
|
+
let _recoverMember_decorators;
|
|
89
94
|
let _updateMember_decorators;
|
|
90
95
|
let _changeTask_decorators;
|
|
91
96
|
let _joinChannel_decorators;
|
|
92
97
|
let _removeChannelMember_decorators;
|
|
93
98
|
let _sendMessage_decorators;
|
|
99
|
+
let _putAttachment_decorators;
|
|
100
|
+
let _getAttachment_decorators;
|
|
94
101
|
let _reply_decorators;
|
|
95
102
|
let _changeAttention_decorators;
|
|
96
103
|
let _inbox_decorators;
|
|
@@ -101,16 +108,20 @@ let AgentTeam = (() => {
|
|
|
101
108
|
return class AgentTeam extends _classSuper {
|
|
102
109
|
static {
|
|
103
110
|
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
111
|
+
_resolveTaskRefs_decorators = [Remote('resolveTaskRefs')];
|
|
104
112
|
_membersForClient_decorators = [Remote('members')];
|
|
105
113
|
_changes_decorators = [Remote('changes')];
|
|
106
114
|
_createChannel_decorators = [Remote('createChannel')];
|
|
107
115
|
_updateChannel_decorators = [Remote('updateChannel')];
|
|
108
116
|
_addMember_decorators = [Remote('addMember')];
|
|
117
|
+
_recoverMember_decorators = [Remote('recoverMember')];
|
|
109
118
|
_updateMember_decorators = [Remote('updateMember')];
|
|
110
119
|
_changeTask_decorators = [Remote('changeTask')];
|
|
111
120
|
_joinChannel_decorators = [Remote('joinChannel')];
|
|
112
121
|
_removeChannelMember_decorators = [Remote('removeChannelMember')];
|
|
113
122
|
_sendMessage_decorators = [Remote('sendMessage')];
|
|
123
|
+
_putAttachment_decorators = [Remote('putAttachment')];
|
|
124
|
+
_getAttachment_decorators = [Remote('getAttachment')];
|
|
114
125
|
_reply_decorators = [Remote('reply')];
|
|
115
126
|
_changeAttention_decorators = [Remote('changeAttention')];
|
|
116
127
|
_inbox_decorators = [Remote('inbox')];
|
|
@@ -118,16 +129,20 @@ let AgentTeam = (() => {
|
|
|
118
129
|
_threadObservations_decorators = [Remote('threadObservations')];
|
|
119
130
|
_threadHistory_decorators = [Remote('threadHistory')];
|
|
120
131
|
_view_decorators = [Remote('view')];
|
|
132
|
+
__esDecorate(this, null, _resolveTaskRefs_decorators, { kind: "method", name: "resolveTaskRefs", static: false, private: false, access: { has: obj => "resolveTaskRefs" in obj, get: obj => obj.resolveTaskRefs }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
121
133
|
__esDecorate(this, null, _membersForClient_decorators, { kind: "method", name: "membersForClient", static: false, private: false, access: { has: obj => "membersForClient" in obj, get: obj => obj.membersForClient }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
122
134
|
__esDecorate(this, null, _changes_decorators, { kind: "method", name: "changes", static: false, private: false, access: { has: obj => "changes" in obj, get: obj => obj.changes }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
123
135
|
__esDecorate(this, null, _createChannel_decorators, { kind: "method", name: "createChannel", static: false, private: false, access: { has: obj => "createChannel" in obj, get: obj => obj.createChannel }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
124
136
|
__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);
|
|
125
137
|
__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);
|
|
138
|
+
__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);
|
|
126
139
|
__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);
|
|
127
140
|
__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);
|
|
128
141
|
__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);
|
|
129
142
|
__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);
|
|
130
143
|
__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);
|
|
144
|
+
__esDecorate(this, null, _putAttachment_decorators, { kind: "method", name: "putAttachment", static: false, private: false, access: { has: obj => "putAttachment" in obj, get: obj => obj.putAttachment }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
145
|
+
__esDecorate(this, null, _getAttachment_decorators, { kind: "method", name: "getAttachment", static: false, private: false, access: { has: obj => "getAttachment" in obj, get: obj => obj.getAttachment }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
131
146
|
__esDecorate(this, null, _reply_decorators, { kind: "method", name: "reply", static: false, private: false, access: { has: obj => "reply" in obj, get: obj => obj.reply }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
132
147
|
__esDecorate(this, null, _changeAttention_decorators, { kind: "method", name: "changeAttention", static: false, private: false, access: { has: obj => "changeAttention" in obj, get: obj => obj.changeAttention }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
133
148
|
__esDecorate(this, null, _inbox_decorators, { kind: "method", name: "inbox", static: false, private: false, access: { has: obj => "inbox" in obj, get: obj => obj.inbox }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
@@ -149,9 +164,23 @@ let AgentTeam = (() => {
|
|
|
149
164
|
domain = __runInitializers(this, _instanceExtraInitializers);
|
|
150
165
|
ledger;
|
|
151
166
|
handles = new Map();
|
|
167
|
+
/** Live selection refs let model edits take effect without disposing the Session. */
|
|
168
|
+
modelSelections = new Map();
|
|
152
169
|
diagnostics = new Map();
|
|
153
170
|
runtimeErrors = new Map();
|
|
171
|
+
/** Agent ids with a turn in flight; restarts must wait for the boundary. */
|
|
172
|
+
runningAgents = new Set();
|
|
154
173
|
notifiedInbox = new Map();
|
|
174
|
+
attachmentGcTimer;
|
|
175
|
+
recovery = new RecoveryCoordinator({
|
|
176
|
+
inject: (memberId, attempt, kind) => {
|
|
177
|
+
this.ctx.logger.info(`agent-team: automatic recovery steering member '${this.memberLabel(memberId)}' (attempt ${attempt}/${RECOVERY_MAX_ATTEMPTS}, ${kind})`);
|
|
178
|
+
this.injectRecovery(memberId, attempt, kind);
|
|
179
|
+
},
|
|
180
|
+
onStandDown: (memberId, attempts) => {
|
|
181
|
+
this.ctx.logger.warn(`agent-team: member '${this.memberLabel(memberId)}' still failing after ${attempts} automatic recovery attempts; leaving it in error for the operator`);
|
|
182
|
+
},
|
|
183
|
+
});
|
|
155
184
|
lifecycleTail = Promise.resolve();
|
|
156
185
|
accepting = true;
|
|
157
186
|
changeVersion = 0;
|
|
@@ -165,11 +194,20 @@ let AgentTeam = (() => {
|
|
|
165
194
|
const member = this.memberForAgent(agent);
|
|
166
195
|
if (member === undefined)
|
|
167
196
|
return;
|
|
168
|
-
|
|
197
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
198
|
+
this.runtimeErrors.set(agent.id, message);
|
|
199
|
+
const kind = classifyRecoverableError(message);
|
|
200
|
+
if (kind !== undefined)
|
|
201
|
+
this.ctx.logger.warn(`agent-team: member '${member.handle}' hit a recoverable error (${kind}); automatic recovery scheduled`);
|
|
202
|
+
this.recovery.onError(member.memberId, message);
|
|
169
203
|
this.emitChanged([{ kind: 'workspace', workspaceId: member.workspaceId }]);
|
|
170
204
|
});
|
|
171
205
|
this.ctx.on('agent/status', ({ agent, status }) => {
|
|
172
206
|
const member = this.memberForAgent(agent);
|
|
207
|
+
if (status === 'running')
|
|
208
|
+
this.runningAgents.add(agent.id);
|
|
209
|
+
else
|
|
210
|
+
this.runningAgents.delete(agent.id);
|
|
173
211
|
if (status === 'running' && member !== undefined) {
|
|
174
212
|
const recovered = this.runtimeErrors.delete(agent.id);
|
|
175
213
|
if (recovered)
|
|
@@ -177,14 +215,24 @@ let AgentTeam = (() => {
|
|
|
177
215
|
this.emitChanged([{ kind: 'workspace', workspaceId: member.workspaceId }]);
|
|
178
216
|
this.notifyMember(agent);
|
|
179
217
|
}
|
|
218
|
+
// A turn that ends without an error closes any automatic recovery episode.
|
|
219
|
+
if (status === 'idle' && member !== undefined && !this.runtimeErrors.has(agent.id)) {
|
|
220
|
+
this.recovery.onCleanTurnEnd(member.memberId);
|
|
221
|
+
}
|
|
180
222
|
});
|
|
181
223
|
const domain = await this.ctx.storageDomain.open(agentTeamDomainSpec);
|
|
182
224
|
this.ctx.effect(() => async () => {
|
|
183
225
|
this.accepting = false;
|
|
226
|
+
this.recovery.dispose();
|
|
227
|
+
if (this.attachmentGcTimer !== undefined)
|
|
228
|
+
clearInterval(this.attachmentGcTimer);
|
|
229
|
+
this.attachmentGcTimer = undefined;
|
|
184
230
|
this.emitChanged();
|
|
185
231
|
await this.lifecycleTail;
|
|
186
232
|
await Promise.all([...this.handles.values()].map(handle => handle.dispose()));
|
|
187
233
|
this.handles.clear();
|
|
234
|
+
this.modelSelections.clear();
|
|
235
|
+
this.runningAgents.clear();
|
|
188
236
|
await domain.close();
|
|
189
237
|
}, 'agentTeam.dispose');
|
|
190
238
|
this.domain = domain;
|
|
@@ -193,6 +241,7 @@ let AgentTeam = (() => {
|
|
|
193
241
|
const initialization = await ledger.initialize();
|
|
194
242
|
if (initialization.committed)
|
|
195
243
|
this.emitCommitted(initialization.value);
|
|
244
|
+
this.startAttachmentGc(ledger);
|
|
196
245
|
// One metadata listing serves every Member restore; per-member list calls
|
|
197
246
|
// would repeat the same I/O linearly during startup.
|
|
198
247
|
const persistedSessions = new Set((await this.ctx.sessionPersistence.list()).map(header => header.id));
|
|
@@ -216,6 +265,18 @@ let AgentTeam = (() => {
|
|
|
216
265
|
return this.requireLedger().listMembers().map(member => this.memberStatus(member));
|
|
217
266
|
}
|
|
218
267
|
/** Return only this Workspace's current Member projection to the Client. */
|
|
268
|
+
/** Read-only navigation lookup for branded Task refs found in message bodies. */
|
|
269
|
+
resolveTaskRefs(request) {
|
|
270
|
+
this.requireWorkspace(request.workspaceId);
|
|
271
|
+
const seen = new Set();
|
|
272
|
+
const taskRefs = request.taskRefs.filter(taskRef => {
|
|
273
|
+
if (seen.has(taskRef))
|
|
274
|
+
return false;
|
|
275
|
+
seen.add(taskRef);
|
|
276
|
+
return true;
|
|
277
|
+
});
|
|
278
|
+
return Object.freeze({ resolved: Object.freeze(this.requireLedger().resolveTaskRefs(request.workspaceId, taskRefs)) });
|
|
279
|
+
}
|
|
219
280
|
membersForClient(request) {
|
|
220
281
|
this.requireWorkspace(request.workspaceId);
|
|
221
282
|
return this.members()
|
|
@@ -293,6 +354,7 @@ let AgentTeam = (() => {
|
|
|
293
354
|
async addMember(request) {
|
|
294
355
|
return this.enqueueLifecycle(async () => {
|
|
295
356
|
const workspace = this.requireWorkspace(request.workspaceId);
|
|
357
|
+
await this.assertModelRoute(request.model);
|
|
296
358
|
const memberId = `member:${randomUUID()}`;
|
|
297
359
|
const member = Object.freeze({
|
|
298
360
|
memberId,
|
|
@@ -325,6 +387,7 @@ let AgentTeam = (() => {
|
|
|
325
387
|
await handle.dispose();
|
|
326
388
|
this.handles.delete(request.memberId);
|
|
327
389
|
}
|
|
390
|
+
this.modelSelections.delete(request.memberId);
|
|
328
391
|
this.diagnostics.delete(request.memberId);
|
|
329
392
|
this.clearMemberNotificationState(request.memberId);
|
|
330
393
|
return Object.freeze({ receipt: result.value.receipt, status: this.memberStatus(result.value.member) });
|
|
@@ -342,13 +405,96 @@ let AgentTeam = (() => {
|
|
|
342
405
|
});
|
|
343
406
|
}
|
|
344
407
|
/**
|
|
345
|
-
*
|
|
346
|
-
*
|
|
347
|
-
*
|
|
348
|
-
|
|
408
|
+
* Operator nudge for an error-stopped Member: steer a continuation prompt
|
|
409
|
+
* into its live session. Runtime-only — no ledger operation, no suspend.
|
|
410
|
+
* Taking over manually also cancels any pending automatic recovery episode.
|
|
411
|
+
*/
|
|
412
|
+
async recoverMember(request) {
|
|
413
|
+
this.requireAccepting();
|
|
414
|
+
const member = this.requireLedger().getMember(request.memberId);
|
|
415
|
+
if (member === undefined || member.workspaceId !== request.workspaceId)
|
|
416
|
+
throw new Error(`unknown Member '${request.memberId}' in workspace '${request.workspaceId}'`);
|
|
417
|
+
const storedError = this.storedErrorFor(member.memberId);
|
|
418
|
+
this.recovery.stopTracking(request.memberId);
|
|
419
|
+
const kind = classifyRecoverableError(storedError ?? '');
|
|
420
|
+
this.ctx.logger.info(`agent-team: operator asked member '${member.handle}' to resume`);
|
|
421
|
+
this.steerResume(member, this.resumeText(kind ?? 'unspecified failure', { operatorRequested: true }));
|
|
422
|
+
return Object.freeze({ status: this.memberStatus(member) });
|
|
423
|
+
}
|
|
424
|
+
/** Ledger handle for log lines; falls back to the raw id when unknown. */
|
|
425
|
+
memberLabel(memberId) {
|
|
426
|
+
return this.ledger?.getMember(memberId)?.handle ?? memberId;
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Cache GC: uploads referenced by a Message survive 72h from upload so
|
|
430
|
+
* Member agents keep a consumption window; orphans (never sent) go after
|
|
431
|
+
* 24h. Runs once at startup and then daily — in-process only, because the
|
|
432
|
+
* cache is transient by design and rebuilds nothing across restarts.
|
|
433
|
+
*/
|
|
434
|
+
startAttachmentGc(ledger) {
|
|
435
|
+
const sweep = async () => {
|
|
436
|
+
await sweepAttachmentCache(attachmentsRoot(), ledger.referencedAttachmentIds(), Date.now());
|
|
437
|
+
};
|
|
438
|
+
void sweep();
|
|
439
|
+
this.attachmentGcTimer = setInterval(() => { void sweep(); }, 24 * 60 * 60 * 1000);
|
|
440
|
+
this.attachmentGcTimer.unref?.();
|
|
441
|
+
}
|
|
442
|
+
/** Latest runtime error summary for a Member's live session, if any. */
|
|
443
|
+
storedErrorFor(memberId) {
|
|
444
|
+
const handle = this.handles.get(memberId);
|
|
445
|
+
if (handle === undefined)
|
|
446
|
+
return undefined;
|
|
447
|
+
return this.runtimeErrors.get(handle.agent.id);
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Continuation prompt per the recovery design: name the error family,
|
|
451
|
+
* annotate automatic attempts, and on the final attempt ask for a graceful
|
|
452
|
+
* handoff instead of another crash.
|
|
453
|
+
*/
|
|
454
|
+
resumeText(kindLabel, options) {
|
|
455
|
+
const opening = options?.operatorRequested === true ? 'The operator asked you to resume. ' : '';
|
|
456
|
+
const attemptNote = options?.attempt === undefined ? '' : ` (automatic recovery, attempt ${options.attempt}/${RECOVERY_MAX_ATTEMPTS})`;
|
|
457
|
+
let text = `${opening}Your previous turn ended early due to an error (${kindLabel})${attemptNote}. Please continue your work from where it stopped.`;
|
|
458
|
+
if (options?.attempt === RECOVERY_MAX_ATTEMPTS) {
|
|
459
|
+
text += ' If the same error recurs, stop and leave a short handoff note for the operator.';
|
|
460
|
+
}
|
|
461
|
+
return text;
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Steer one continuation message into a Member's live session. Throws when
|
|
465
|
+
* no handle exists so the coordinator stops tracking; appends the inbox
|
|
466
|
+
* snapshot whenever there is anything new to read.
|
|
467
|
+
*/
|
|
468
|
+
steerResume(member, text) {
|
|
469
|
+
const handle = this.handles.get(member.memberId);
|
|
470
|
+
if (handle === undefined)
|
|
471
|
+
throw new Error(`member '${member.handle}' has no active session`);
|
|
472
|
+
const notifications = this.requireLedger().notificationFacts(member.memberId, { workspaceId: member.workspaceId });
|
|
473
|
+
const body = notifications.length === 0 ? text : `${text}\n\n${this.notificationText(notifications, member.memberId)}`;
|
|
474
|
+
const hint = createUserMessage({
|
|
475
|
+
content: [{ type: 'text', text: body }],
|
|
476
|
+
source: { kind: 'plugin', plugin: '@wowyuarm/dsh-agent-team', form: 'notice', summary: 'Recovery: continue your interrupted work.' },
|
|
477
|
+
});
|
|
478
|
+
handle.agent.steer(hint);
|
|
479
|
+
}
|
|
480
|
+
/** Automatic-recovery injection; throwing tells the coordinator the target is gone. */
|
|
481
|
+
injectRecovery(memberId, attempt, kind) {
|
|
482
|
+
const handle = this.handles.get(memberId);
|
|
483
|
+
const agent = handle?.agent;
|
|
484
|
+
const member = agent !== undefined ? this.memberForAgent(agent) : undefined;
|
|
485
|
+
if (agent === undefined || member === undefined || member.state !== 'enabled')
|
|
486
|
+
throw new Error(`member '${memberId}' cannot be recovered automatically`);
|
|
487
|
+
this.steerResume(member, this.resumeText(kind, { attempt }));
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* Human edit of one Member's mutable facts. A live model selection is
|
|
491
|
+
* updated in place: disposing an Agent emits session/disposed, which makes
|
|
492
|
+
* the Web Client permanently mark the same Session id unavailable even when
|
|
493
|
+
* Team immediately recreates it.
|
|
349
494
|
*/
|
|
350
495
|
async updateMember(request) {
|
|
351
496
|
return this.enqueueLifecycle(async () => {
|
|
497
|
+
await this.assertModelRoute(request.model);
|
|
352
498
|
const previous = this.requireLedger().getMember(request.memberId);
|
|
353
499
|
const result = await this.requireLedger().updateMember({ ...request, actor: agentTeamHumanActor() });
|
|
354
500
|
if (result.committed)
|
|
@@ -356,9 +502,10 @@ let AgentTeam = (() => {
|
|
|
356
502
|
const stored = result.value.member;
|
|
357
503
|
const active = this.handles.get(request.memberId);
|
|
358
504
|
if (active !== undefined && !isDeepStrictEqual(previous?.model ?? undefined, stored.model ?? undefined)) {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
505
|
+
const selection = this.modelSelections.get(request.memberId);
|
|
506
|
+
if (selection === undefined)
|
|
507
|
+
throw new Error(`Agent Member '${stored.handle}' has no live model selection`);
|
|
508
|
+
selection.current = stored.model ?? this.ctx.agentDefaultModel.currentSelection();
|
|
362
509
|
}
|
|
363
510
|
return Object.freeze({ receipt: result.value.receipt, status: this.memberStatus(stored) });
|
|
364
511
|
});
|
|
@@ -374,6 +521,7 @@ let AgentTeam = (() => {
|
|
|
374
521
|
await handle.dispose();
|
|
375
522
|
this.handles.delete(request.memberId);
|
|
376
523
|
}
|
|
524
|
+
this.modelSelections.delete(request.memberId);
|
|
377
525
|
this.diagnostics.delete(request.memberId);
|
|
378
526
|
this.clearMemberNotificationState(request.memberId);
|
|
379
527
|
await this.cleanupRemovedMember(result.value.member);
|
|
@@ -413,15 +561,84 @@ let AgentTeam = (() => {
|
|
|
413
561
|
async sendMessage(request) {
|
|
414
562
|
this.requireAccepting();
|
|
415
563
|
this.requireWorkspace(request.workspaceId);
|
|
416
|
-
const
|
|
564
|
+
const metadata = await this.resolveMessageAttachments(request);
|
|
565
|
+
const result = await this.requireLedger().sendMessage({
|
|
566
|
+
...request, body: this.appendAttachmentLines(request.body, metadata),
|
|
567
|
+
...(metadata.length === 0 ? {} : { resolvedAttachments: metadata }),
|
|
568
|
+
actor: agentTeamHumanActor(),
|
|
569
|
+
});
|
|
417
570
|
this.emitCommittedOutcome(result);
|
|
418
571
|
return result.value;
|
|
419
572
|
}
|
|
573
|
+
/**
|
|
574
|
+
* Resolve uploaded ids and agent-supplied absolute paths into one attachment
|
|
575
|
+
* metadata list. Paths are all validated before anything is copied, so one
|
|
576
|
+
* rejection leaves the cache untouched and the message uncommitted.
|
|
577
|
+
*/
|
|
578
|
+
async resolveMessageAttachments(request) {
|
|
579
|
+
const fromPaths = [];
|
|
580
|
+
if (request.attachmentPaths !== undefined && request.attachmentPaths.length > 0) {
|
|
581
|
+
for (const absolutePath of request.attachmentPaths)
|
|
582
|
+
await validatePathAttachment(absolutePath);
|
|
583
|
+
for (const absolutePath of request.attachmentPaths)
|
|
584
|
+
fromPaths.push(Object.freeze(await copyPathAttachment(attachmentsRoot(), absolutePath)));
|
|
585
|
+
}
|
|
586
|
+
return [...fromPaths, ...await this.prepareAttachments(request.attachments)];
|
|
587
|
+
}
|
|
588
|
+
/** Verify requested attachment ids against the cache. */
|
|
589
|
+
async prepareAttachments(requested) {
|
|
590
|
+
if (requested === undefined || requested.length === 0)
|
|
591
|
+
return [];
|
|
592
|
+
const metadata = [];
|
|
593
|
+
for (const attachmentId of requested) {
|
|
594
|
+
const stored = await readAttachment(attachmentsRoot(), attachmentId);
|
|
595
|
+
if (stored === undefined)
|
|
596
|
+
throw new Error(`attachment '${attachmentId}' is not in the upload cache`);
|
|
597
|
+
metadata.push(Object.freeze({ attachmentId, name: stored.name, byteSize: stored.byteSize, mediaType: stored.mediaType }));
|
|
598
|
+
}
|
|
599
|
+
return metadata;
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* Derive the stored body: one machine-facing `[attachment] <absolute path>`
|
|
603
|
+
* line per attachment appended to the member-facing text.
|
|
604
|
+
*/
|
|
605
|
+
appendAttachmentLines(body, metadata) {
|
|
606
|
+
const trimmed = body.trim();
|
|
607
|
+
if (metadata.length === 0)
|
|
608
|
+
return trimmed;
|
|
609
|
+
const lines = metadata.map(attachment => `[attachment] ${dshHomePath('agent-team', 'attachments', 'v1', attachment.attachmentId, attachment.name)}`);
|
|
610
|
+
return `${trimmed}\n${lines.join('\n')}`;
|
|
611
|
+
}
|
|
612
|
+
/** Upload one composer attachment into the cache; bytes are immutable once written. */
|
|
613
|
+
async putAttachment(request) {
|
|
614
|
+
this.requireAccepting();
|
|
615
|
+
this.requireWorkspace(request.workspaceId);
|
|
616
|
+
const bytes = Buffer.from(request.bytesBase64, 'base64');
|
|
617
|
+
if (bytes.byteLength === 0)
|
|
618
|
+
throw new Error('attachment must not be empty');
|
|
619
|
+
if (bytes.byteLength > ATTACHMENT_MAX_BYTES)
|
|
620
|
+
throw new Error(`attachment exceeds the ${ATTACHMENT_MAX_BYTES} byte limit`);
|
|
621
|
+
const mediaType = sanitizeMediaType(request.mediaType);
|
|
622
|
+
const attachmentId = newAttachmentId();
|
|
623
|
+
return Object.freeze(await writeAttachment(attachmentsRoot(), attachmentId, request.name, mediaType, bytes));
|
|
624
|
+
}
|
|
625
|
+
/** Read one cached attachment back for client display; gone entries throw and the UI degrades to a chip. */
|
|
626
|
+
async getAttachment(request) {
|
|
627
|
+
const stored = await readAttachment(attachmentsRoot(), request.attachmentId);
|
|
628
|
+
if (stored === undefined)
|
|
629
|
+
throw new Error(`attachment '${request.attachmentId}' is no longer cached`);
|
|
630
|
+
return Object.freeze({ name: stored.name, mediaType: stored.mediaType, byteSize: stored.byteSize, bytesBase64: stored.bytes.toString('base64') });
|
|
631
|
+
}
|
|
420
632
|
/** Human existing-Thread reply; unread and revision conflicts are business outcomes. */
|
|
421
633
|
async reply(request) {
|
|
422
634
|
this.requireAccepting();
|
|
423
635
|
this.requireWorkspace(request.workspaceId);
|
|
424
|
-
const
|
|
636
|
+
const metadata = await this.resolveMessageAttachments(request);
|
|
637
|
+
const result = await this.requireLedger().reply({
|
|
638
|
+
...request, body: this.appendAttachmentLines(request.body, metadata),
|
|
639
|
+
...(metadata.length === 0 ? {} : { resolvedAttachments: metadata }),
|
|
640
|
+
actor: agentTeamHumanActor(),
|
|
641
|
+
});
|
|
425
642
|
this.emitCommittedOutcome(result);
|
|
426
643
|
return result.value;
|
|
427
644
|
}
|
|
@@ -468,7 +685,12 @@ let AgentTeam = (() => {
|
|
|
468
685
|
this.requireAccepting();
|
|
469
686
|
const actor = this.memberActor(agent);
|
|
470
687
|
this.requireAgentWorkspace(actor, request.workspaceId);
|
|
471
|
-
const
|
|
688
|
+
const metadata = await this.resolveMessageAttachments(request);
|
|
689
|
+
const result = await this.requireLedger().sendMessage({
|
|
690
|
+
...request, body: this.appendAttachmentLines(request.body, metadata),
|
|
691
|
+
...(metadata.length === 0 ? {} : { resolvedAttachments: metadata }),
|
|
692
|
+
actor,
|
|
693
|
+
});
|
|
472
694
|
this.emitCommittedOutcome(result);
|
|
473
695
|
return result.value;
|
|
474
696
|
}
|
|
@@ -477,7 +699,12 @@ let AgentTeam = (() => {
|
|
|
477
699
|
this.requireAccepting();
|
|
478
700
|
const actor = this.memberActor(agent);
|
|
479
701
|
this.requireAgentWorkspace(actor, request.workspaceId);
|
|
480
|
-
const
|
|
702
|
+
const metadata = await this.resolveMessageAttachments(request);
|
|
703
|
+
const result = await this.requireLedger().reply({
|
|
704
|
+
...request, body: this.appendAttachmentLines(request.body, metadata),
|
|
705
|
+
...(metadata.length === 0 ? {} : { resolvedAttachments: metadata }),
|
|
706
|
+
actor,
|
|
707
|
+
});
|
|
481
708
|
this.emitCommittedOutcome(result);
|
|
482
709
|
return result.value;
|
|
483
710
|
}
|
|
@@ -565,6 +792,26 @@ let AgentTeam = (() => {
|
|
|
565
792
|
if (this.requireLedger().getMember(actor.memberId)?.workspaceId !== workspaceId)
|
|
566
793
|
throw new Error('Member cannot mutate another Workspace');
|
|
567
794
|
}
|
|
795
|
+
/**
|
|
796
|
+
* Validate a pinned model route's reasoning effort against the adapter's own
|
|
797
|
+
* metadata when the LLM service is reachable; unknown routes defer to the
|
|
798
|
+
* LLM layer's runtime check at call time.
|
|
799
|
+
*/
|
|
800
|
+
async assertModelRoute(model) {
|
|
801
|
+
if (model === undefined || model.reasoningEffort === undefined)
|
|
802
|
+
return;
|
|
803
|
+
try {
|
|
804
|
+
const resolved = await this.ctx.llm.resolveModelInfo(model.provider, model.model);
|
|
805
|
+
const efforts = resolved.reasoning?.efforts ?? [];
|
|
806
|
+
if (efforts.length > 0 && !efforts.some(effort => effort.id === model.reasoningEffort)) {
|
|
807
|
+
throw new Error(`reasoning effort '${model.reasoningEffort}' is not supported by ${model.provider}/${model.model}`);
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
catch (error) {
|
|
811
|
+
if (error instanceof Error && error.message.includes('is not supported by'))
|
|
812
|
+
throw error;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
568
815
|
async activateMember(member, knownWorkspacePath, knownSessions) {
|
|
569
816
|
if (this.handles.has(member.memberId))
|
|
570
817
|
return;
|
|
@@ -575,9 +822,16 @@ let AgentTeam = (() => {
|
|
|
575
822
|
await this.initializePrivateMemory(member.privateMemoryPath);
|
|
576
823
|
const persisted = knownSessions !== undefined ? knownSessions.has(member.sessionId)
|
|
577
824
|
: (await this.ctx.sessionPersistence.list()).some(header => header.id === member.sessionId);
|
|
825
|
+
// AgentOptions declares only provider/model. Install the full selection
|
|
826
|
+
// through the public Agent model-selection seam so reasoning effort is
|
|
827
|
+
// applied to the next request and not lost during activation.
|
|
828
|
+
const selection = member.model ?? this.ctx.agentDefaultModel.currentSelection();
|
|
829
|
+
const agentOptions = { provider: selection.provider, model: selection.model };
|
|
830
|
+
const selected = { current: selection, assembled: undefined };
|
|
578
831
|
const setup = async (agentCtx) => {
|
|
579
832
|
await this.ctx.agentPresets.mount(agentCtx, member.presetId);
|
|
580
833
|
this.validateMemberPreset(agentCtx);
|
|
834
|
+
installModelSelection(agentCtx, selected);
|
|
581
835
|
return {
|
|
582
836
|
commit: () => {
|
|
583
837
|
const agent = agentCtx.agent;
|
|
@@ -588,10 +842,6 @@ let AgentTeam = (() => {
|
|
|
588
842
|
},
|
|
589
843
|
};
|
|
590
844
|
};
|
|
591
|
-
// A Member-pinned selection wins; otherwise the Host default applies at
|
|
592
|
-
// every activation, so an override survives restarts and a cleared one
|
|
593
|
-
// re-reads the live default.
|
|
594
|
-
const agentOptions = member.model ?? this.ctx.agentDefaultModel.currentSelection();
|
|
595
845
|
created = persisted
|
|
596
846
|
? await this.ctx.agents.resume({ resumeSessionId: member.sessionId, agentOptions, setup })
|
|
597
847
|
: await this.ctx.agents.create({
|
|
@@ -602,12 +852,14 @@ let AgentTeam = (() => {
|
|
|
602
852
|
});
|
|
603
853
|
await workspace.attachSession(member.sessionId);
|
|
604
854
|
this.handles.set(member.memberId, created);
|
|
855
|
+
this.modelSelections.set(member.memberId, selected);
|
|
605
856
|
this.diagnostics.delete(member.memberId);
|
|
606
857
|
this.nameMemberSession(member, created.agent);
|
|
607
858
|
this.notifyMember(created.agent);
|
|
608
859
|
}
|
|
609
860
|
catch (error) {
|
|
610
861
|
await created?.dispose();
|
|
862
|
+
this.modelSelections.delete(member.memberId);
|
|
611
863
|
this.diagnostics.set(member.memberId, error instanceof Error ? error.message : String(error));
|
|
612
864
|
}
|
|
613
865
|
finally {
|
|
@@ -729,7 +981,7 @@ let AgentTeam = (() => {
|
|
|
729
981
|
if (existingHint !== undefined)
|
|
730
982
|
agent.inbox.remove(existingHint.id);
|
|
731
983
|
const hint = createUserMessage({
|
|
732
|
-
content: [{ type: 'text', text: this.notificationText(notifications) }],
|
|
984
|
+
content: [{ type: 'text', text: this.notificationText(notifications, member.memberId) }],
|
|
733
985
|
source: { kind: 'plugin', plugin: '@wowyuarm/dsh-agent-team', form: 'notice', summary: 'Team Inbox has unread work.' },
|
|
734
986
|
});
|
|
735
987
|
this.notifiedInbox.set(member.memberId, signature);
|
|
@@ -741,7 +993,7 @@ let AgentTeam = (() => {
|
|
|
741
993
|
throw error;
|
|
742
994
|
}
|
|
743
995
|
}
|
|
744
|
-
notificationText(notifications) {
|
|
996
|
+
notificationText(notifications, readerId) {
|
|
745
997
|
const maxCharacters = 32 * 1024;
|
|
746
998
|
const sections = ['Team Inbox has unread work.'];
|
|
747
999
|
let characterCount = sections[0].length;
|
|
@@ -767,19 +1019,19 @@ let AgentTeam = (() => {
|
|
|
767
1019
|
? 'human' : this.requireLedger().getMember(fact.message.sender)?.handle ?? fact.message.sender;
|
|
768
1020
|
const detail = ['Direct Team mention', `From: ${sender}`, `Channel: ${item.channelRef}`,
|
|
769
1021
|
`Task: ${item.task.taskRef}`, `Thread: ${item.thread.threadRef}`, `Message ref: ${fact.message.messageRef}`,
|
|
770
|
-
`
|
|
1022
|
+
`Message: ${this.boundedNotificationBody(fact.message.body)}`].join('\n');
|
|
771
1023
|
if (append(detail))
|
|
772
1024
|
detailedFactCount += 1;
|
|
773
1025
|
}
|
|
774
1026
|
else if (fact.kind === 'activity') {
|
|
775
|
-
const detail = `${this.activityNotification(fact.activity)}\nThread: ${item.thread.threadRef}
|
|
1027
|
+
const detail = `${this.activityNotification(fact.activity, readerId)}\nThread: ${item.thread.threadRef}`;
|
|
776
1028
|
if (append(detail))
|
|
777
1029
|
detailedFactCount += 1;
|
|
778
1030
|
}
|
|
779
1031
|
}
|
|
780
1032
|
const ordinaryCount = facts.filter(entry => entry.fact.kind === 'message' && !entry.direct).length;
|
|
781
1033
|
if (ordinaryCount > 0)
|
|
782
|
-
append(`Task ${item.task.taskRef}: ${ordinaryCount} unread update${ordinaryCount === 1 ? '' : 's'}
|
|
1034
|
+
append(`Task ${item.task.taskRef}: ${ordinaryCount} unread update${ordinaryCount === 1 ? '' : 's'}.`);
|
|
783
1035
|
}
|
|
784
1036
|
if (omitted)
|
|
785
1037
|
sections.push('More unread work remains in team_inbox; the automatic context is bounded.');
|
|
@@ -790,9 +1042,15 @@ let AgentTeam = (() => {
|
|
|
790
1042
|
const limit = 8 * 1024;
|
|
791
1043
|
return body.length <= limit ? body : `${body.slice(0, limit)}\n[Message body truncated; use team_thread read for the full Message.]`;
|
|
792
1044
|
}
|
|
793
|
-
activityNotification(activity) {
|
|
1045
|
+
activityNotification(activity, readerId) {
|
|
794
1046
|
const actor = activity.actor === AGENT_TEAM_HUMAN_MEMBER_ID
|
|
795
1047
|
? 'human' : this.requireLedger().getMember(activity.actor)?.handle ?? activity.actor;
|
|
1048
|
+
// Early acceptance completes the reader's own open Claim inside the accept
|
|
1049
|
+
// operation: say so plainly, or the owner keeps working on a done Task.
|
|
1050
|
+
if (activity.kind === 'accept' && activity.actor === AGENT_TEAM_HUMAN_MEMBER_ID && readerId !== undefined
|
|
1051
|
+
&& activity.completedClaimRefs?.some(claimRef => this.requireLedger().getClaim(claimRef)?.owner === readerId)) {
|
|
1052
|
+
return `Team Task update\n${actor} accepted Task ${activity.taskRef} and your open Claim was completed with it. No further work is needed.`;
|
|
1053
|
+
}
|
|
796
1054
|
if (activity.kind === 'claim' || activity.kind === 'done' || activity.kind === 'release') {
|
|
797
1055
|
return `Team Task update\n${actor} ${activity.kind} Claim ${activity.claimRef} on Task ${activity.taskRef}.`;
|
|
798
1056
|
}
|