@wowyuarm/dsh-agent-team 0.1.1 → 0.1.3
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 +1 -0
- package/README.zh.md +2 -0
- package/package.json +42 -25
- package/packages/agent-team/README.md +1 -1
- package/packages/agent-team/README.zh.md +1 -1
- package/packages/agent-team/lib/attachments.js +11 -12
- package/packages/agent-team/lib/auto-compaction.js +163 -0
- package/packages/agent-team/lib/index.js +262 -189
- package/packages/agent-team/lib/ledger.js +140 -109
- package/packages/agent-team/lib/recovery.js +30 -35
- package/packages/agent-team/lib/spec.js +10 -4
- package/packages/agent-team/lib/typert.host.js +68 -71
- package/packages/agent-team/lib/typert.remote-client.d.ts +3 -3
- package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
- package/packages/agent-team/lib/typert.remote-client.js +41 -56
- package/packages/agent-team/lib/types/attachments.d.ts +2 -2
- package/packages/agent-team/lib/types/attachments.d.ts.map +1 -1
- package/packages/agent-team/lib/types/auto-compaction.d.ts +38 -0
- package/packages/agent-team/lib/types/auto-compaction.d.ts.map +1 -0
- package/packages/agent-team/lib/types/index.d.ts +49 -24
- package/packages/agent-team/lib/types/index.d.ts.map +1 -1
- package/packages/agent-team/lib/types/ledger.d.ts +24 -11
- package/packages/agent-team/lib/types/ledger.d.ts.map +1 -1
- package/packages/agent-team/lib/types/recovery.d.ts +15 -16
- package/packages/agent-team/lib/types/recovery.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 +20 -12
- package/packages/agent-team/lib/types/types.d.ts.map +1 -1
- package/packages/agent-team/preset/team-member/agent.cordis.yml +8 -3
- package/packages/client-agent-team/README.md +3 -2
- package/packages/client-agent-team/README.zh.md +3 -2
- package/packages/client-agent-team/lib/client.js +1507 -525
- package/packages/client-agent-team/lib/client.js.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts +1 -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 +24 -182
- package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts +3 -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 +27 -5
- package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.js +19 -4
- package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamComposer.js +22 -2
- 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/TeamMemberEditor.d.ts +34 -0
- package/packages/client-agent-team/lib/types/client/TeamMemberEditor.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/TeamMemberEditor.js +169 -0
- package/packages/client-agent-team/lib/types/client/TeamMemberSessionComposer.d.ts +17 -0
- package/packages/client-agent-team/lib/types/client/TeamMemberSessionComposer.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/TeamMemberSessionComposer.js +120 -0
- package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts +7 -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 +170 -18
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts +4 -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 +70 -13
- 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 +2 -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 +72 -2
- package/packages/client-agent-team/lib/types/client/locales.d.ts +8 -2
- package/packages/client-agent-team/lib/types/client/locales.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/locales.js +8 -2
- package/packages/client-agent-team/lib/types/client/member-session-input.d.ts +22 -0
- package/packages/client-agent-team/lib/types/client/member-session-input.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/member-session-input.js +98 -0
- package/packages/client-agent-team/lib/types/client/requests.d.ts +16 -0
- package/packages/client-agent-team/lib/types/client/requests.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/requests.js +22 -0
- 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 +2 -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 +25 -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 +77 -0
- package/packages/client-agent-team/lib/types/client/team-formatters.d.ts +54 -1
- 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 +83 -2
- package/packages/client-agent-team/lib/types/client/team-membership.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/team-membership.js +2 -1
- package/packages/tool-agent-team/lib/index.js +4 -4
- package/packages/client-agent-team/lib/types/client/TeamInboxPanel.d.ts +0 -13
- package/packages/client-agent-team/lib/types/client/TeamInboxPanel.d.ts.map +0 -1
- package/packages/client-agent-team/lib/types/client/TeamInboxPanel.js +0 -90
|
@@ -44,20 +44,26 @@ 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';
|
|
53
|
-
import { ATTACHMENT_MAX_BYTES, attachmentsRoot, copyPathAttachment, newAttachmentId, readAttachment, sanitizeMediaType, sweepAttachmentCache, validatePathAttachment, writeAttachment } from "./attachments.js";
|
|
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";
|
|
54
56
|
import { AGENT_TEAM_HUMAN_MEMBER_ID, AgentTeamLedger, agentTeamHumanActor } from "./ledger.js";
|
|
55
|
-
import { classifyRecoverableError, RecoveryCoordinator,
|
|
57
|
+
import { classifyRecoverableError, RecoveryCoordinator, RECOVERY_MAX_CONSECUTIVE_ERRORS } from "./recovery.js";
|
|
56
58
|
import { agentTeamDomainSpec } from "./spec.js";
|
|
57
59
|
export { agentTeamDomainSpec, agentTeamOperationSchema } from "./spec.js";
|
|
58
60
|
export { AGENT_TEAM_HUMAN_MEMBER_ID, AGENT_TEAM_INITIALIZE_REQUEST_ID } from "./ledger.js";
|
|
59
61
|
/** Process-stable marker carried by the final Team message tool definition. */
|
|
60
62
|
export const AGENT_TEAM_PRESET_MARKER = Symbol.for('@wowyuarm/dsh-agent-team.preset');
|
|
63
|
+
const AGENT_TEAM_PLUGIN_ID = '@wowyuarm/dsh-agent-team';
|
|
64
|
+
const INBOX_NOTICE_SUMMARY = 'Team Inbox has unread work.';
|
|
65
|
+
const RECOVERY_NOTICE_SUMMARY = 'Recovery: continue your interrupted work.';
|
|
66
|
+
const ORPHANED_MEMBER_DIAGNOSTIC = 'Member preset composition was lost after a reload; its tools are unavailable. Resume rebuilds the member in place.';
|
|
61
67
|
function sameChangeScope(left, right) {
|
|
62
68
|
if (left.kind === 'workspace' && right.kind === 'workspace')
|
|
63
69
|
return left.workspaceId === right.workspaceId;
|
|
@@ -83,13 +89,13 @@ export const AGENT_TEAM_TOOL_NAMES = Object.freeze([
|
|
|
83
89
|
let AgentTeam = (() => {
|
|
84
90
|
let _classSuper = TypertRemoteService;
|
|
85
91
|
let _instanceExtraInitializers = [];
|
|
92
|
+
let _resolveTaskRefs_decorators;
|
|
86
93
|
let _membersForClient_decorators;
|
|
87
94
|
let _changes_decorators;
|
|
88
95
|
let _createChannel_decorators;
|
|
89
96
|
let _updateChannel_decorators;
|
|
90
97
|
let _addMember_decorators;
|
|
91
98
|
let _recoverMember_decorators;
|
|
92
|
-
let _restartMember_decorators;
|
|
93
99
|
let _updateMember_decorators;
|
|
94
100
|
let _changeTask_decorators;
|
|
95
101
|
let _joinChannel_decorators;
|
|
@@ -107,13 +113,13 @@ let AgentTeam = (() => {
|
|
|
107
113
|
return class AgentTeam extends _classSuper {
|
|
108
114
|
static {
|
|
109
115
|
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
116
|
+
_resolveTaskRefs_decorators = [Remote('resolveTaskRefs')];
|
|
110
117
|
_membersForClient_decorators = [Remote('members')];
|
|
111
118
|
_changes_decorators = [Remote('changes')];
|
|
112
119
|
_createChannel_decorators = [Remote('createChannel')];
|
|
113
120
|
_updateChannel_decorators = [Remote('updateChannel')];
|
|
114
121
|
_addMember_decorators = [Remote('addMember')];
|
|
115
122
|
_recoverMember_decorators = [Remote('recoverMember')];
|
|
116
|
-
_restartMember_decorators = [Remote('restartMember')];
|
|
117
123
|
_updateMember_decorators = [Remote('updateMember')];
|
|
118
124
|
_changeTask_decorators = [Remote('changeTask')];
|
|
119
125
|
_joinChannel_decorators = [Remote('joinChannel')];
|
|
@@ -128,13 +134,13 @@ let AgentTeam = (() => {
|
|
|
128
134
|
_threadObservations_decorators = [Remote('threadObservations')];
|
|
129
135
|
_threadHistory_decorators = [Remote('threadHistory')];
|
|
130
136
|
_view_decorators = [Remote('view')];
|
|
137
|
+
__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);
|
|
131
138
|
__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);
|
|
132
139
|
__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);
|
|
133
140
|
__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);
|
|
134
141
|
__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);
|
|
135
142
|
__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);
|
|
136
143
|
__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);
|
|
137
|
-
__esDecorate(this, null, _restartMember_decorators, { kind: "method", name: "restartMember", static: false, private: false, access: { has: obj => "restartMember" in obj, get: obj => obj.restartMember }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
138
144
|
__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);
|
|
139
145
|
__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);
|
|
140
146
|
__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);
|
|
@@ -163,19 +169,28 @@ let AgentTeam = (() => {
|
|
|
163
169
|
domain = __runInitializers(this, _instanceExtraInitializers);
|
|
164
170
|
ledger;
|
|
165
171
|
handles = new Map();
|
|
166
|
-
|
|
167
|
-
|
|
172
|
+
/** Live selection refs let model edits take effect without disposing the Session. */
|
|
173
|
+
modelSelections = new Map();
|
|
174
|
+
/**
|
|
175
|
+
* Why one Member shows error presence, per failure source. Reads prefer
|
|
176
|
+
* activation, then runtime, then compaction; slots clear independently, so
|
|
177
|
+
* a recovered runtime error re-reveals an outstanding compaction failure.
|
|
178
|
+
* Keyed by Member rather than Session so a restarted Session cannot leak
|
|
179
|
+
* stale keys.
|
|
180
|
+
*/
|
|
181
|
+
memberFailures = new Map();
|
|
182
|
+
autoCompaction;
|
|
168
183
|
/** Agent ids with a turn in flight; restarts must wait for the boundary. */
|
|
169
184
|
runningAgents = new Set();
|
|
170
185
|
notifiedInbox = new Map();
|
|
171
186
|
attachmentGcTimer;
|
|
172
187
|
recovery = new RecoveryCoordinator({
|
|
173
|
-
|
|
174
|
-
this.ctx.logger.info(`agent-team: automatic recovery
|
|
175
|
-
this.injectRecovery(memberId
|
|
188
|
+
wake: memberId => {
|
|
189
|
+
this.ctx.logger.info(`agent-team: automatic recovery wakeup for member '${this.memberLabel(memberId)}' after consecutive recoverable failures`);
|
|
190
|
+
this.injectRecovery(memberId);
|
|
176
191
|
},
|
|
177
|
-
onStandDown: (memberId,
|
|
178
|
-
this.ctx.logger.warn(`agent-team: member '${this.memberLabel(memberId)}'
|
|
192
|
+
onStandDown: (memberId, consecutiveFailures) => {
|
|
193
|
+
this.ctx.logger.warn(`agent-team: member '${this.memberLabel(memberId)}' reached ${consecutiveFailures}/${RECOVERY_MAX_CONSECUTIVE_ERRORS} consecutive recoverable failures; leaving it in error for the operator`);
|
|
179
194
|
},
|
|
180
195
|
});
|
|
181
196
|
lifecycleTail = Promise.resolve();
|
|
@@ -184,6 +199,21 @@ let AgentTeam = (() => {
|
|
|
184
199
|
changeWaiters = new Set();
|
|
185
200
|
constructor(ctx) {
|
|
186
201
|
super(ctx, 'agentTeam');
|
|
202
|
+
this.autoCompaction = new AutoCompactionCoordinator({
|
|
203
|
+
agentForMember: memberId => this.handles.get(memberId)?.agent,
|
|
204
|
+
compactionForAgent: agent => this.ctx.agentPresets.serviceFor(agent, 'compaction'),
|
|
205
|
+
reactivate: memberId => this.reactivateMember(memberId),
|
|
206
|
+
failed: (memberId, _sessionId, diagnostic) => {
|
|
207
|
+
this.setMemberFailure(memberId, 'compaction', diagnostic);
|
|
208
|
+
this.emitAutoCompactionChanged(memberId);
|
|
209
|
+
},
|
|
210
|
+
cleared: memberId => {
|
|
211
|
+
if (!this.clearMemberFailure(memberId, 'compaction'))
|
|
212
|
+
return;
|
|
213
|
+
this.emitAutoCompactionChanged(memberId);
|
|
214
|
+
},
|
|
215
|
+
log: message => { this.ctx.logger.warn(`agent-team: ${message}`); },
|
|
216
|
+
});
|
|
187
217
|
}
|
|
188
218
|
/** Open the durable ledger and restore every enabled Member independently. */
|
|
189
219
|
async [Service.init]() {
|
|
@@ -192,10 +222,10 @@ let AgentTeam = (() => {
|
|
|
192
222
|
if (member === undefined)
|
|
193
223
|
return;
|
|
194
224
|
const message = error instanceof Error ? error.message : String(error);
|
|
195
|
-
this.
|
|
225
|
+
this.setMemberFailure(member.memberId, 'runtime', message);
|
|
196
226
|
const kind = classifyRecoverableError(message);
|
|
197
227
|
if (kind !== undefined)
|
|
198
|
-
this.ctx.logger.warn(`agent-team: member '${member.handle}' hit a recoverable
|
|
228
|
+
this.ctx.logger.warn(`agent-team: member '${member.handle}' hit a recoverable ${kind} error; recording a consecutive error occurrence`);
|
|
199
229
|
this.recovery.onError(member.memberId, message);
|
|
200
230
|
this.emitChanged([{ kind: 'workspace', workspaceId: member.workspaceId }]);
|
|
201
231
|
});
|
|
@@ -206,14 +236,14 @@ let AgentTeam = (() => {
|
|
|
206
236
|
else
|
|
207
237
|
this.runningAgents.delete(agent.id);
|
|
208
238
|
if (status === 'running' && member !== undefined) {
|
|
209
|
-
const recovered = this.
|
|
239
|
+
const recovered = this.clearMemberFailure(member.memberId, 'runtime');
|
|
210
240
|
if (recovered)
|
|
211
241
|
this.notifiedInbox.delete(member.memberId);
|
|
212
242
|
this.emitChanged([{ kind: 'workspace', workspaceId: member.workspaceId }]);
|
|
213
243
|
this.notifyMember(agent);
|
|
214
244
|
}
|
|
215
245
|
// A turn that ends without an error closes any automatic recovery episode.
|
|
216
|
-
if (status === 'idle' && member !== undefined &&
|
|
246
|
+
if (status === 'idle' && member !== undefined && this.memberFailures.get(member.memberId)?.runtime === undefined) {
|
|
217
247
|
this.recovery.onCleanTurnEnd(member.memberId);
|
|
218
248
|
}
|
|
219
249
|
});
|
|
@@ -221,6 +251,7 @@ let AgentTeam = (() => {
|
|
|
221
251
|
this.ctx.effect(() => async () => {
|
|
222
252
|
this.accepting = false;
|
|
223
253
|
this.recovery.dispose();
|
|
254
|
+
await this.autoCompaction.dispose();
|
|
224
255
|
if (this.attachmentGcTimer !== undefined)
|
|
225
256
|
clearInterval(this.attachmentGcTimer);
|
|
226
257
|
this.attachmentGcTimer = undefined;
|
|
@@ -228,6 +259,7 @@ let AgentTeam = (() => {
|
|
|
228
259
|
await this.lifecycleTail;
|
|
229
260
|
await Promise.all([...this.handles.values()].map(handle => handle.dispose()));
|
|
230
261
|
this.handles.clear();
|
|
262
|
+
this.modelSelections.clear();
|
|
231
263
|
this.runningAgents.clear();
|
|
232
264
|
await domain.close();
|
|
233
265
|
}, 'agentTeam.dispose');
|
|
@@ -260,6 +292,18 @@ let AgentTeam = (() => {
|
|
|
260
292
|
members() {
|
|
261
293
|
return this.requireLedger().listMembers().map(member => this.memberStatus(member));
|
|
262
294
|
}
|
|
295
|
+
/** Read-only navigation lookup for branded Task refs found in message bodies. */
|
|
296
|
+
resolveTaskRefs(request) {
|
|
297
|
+
this.requireWorkspace(request.workspaceId);
|
|
298
|
+
const seen = new Set();
|
|
299
|
+
const taskRefs = request.taskRefs.filter(taskRef => {
|
|
300
|
+
if (seen.has(taskRef))
|
|
301
|
+
return false;
|
|
302
|
+
seen.add(taskRef);
|
|
303
|
+
return true;
|
|
304
|
+
});
|
|
305
|
+
return Object.freeze({ resolved: Object.freeze(this.requireLedger().resolveTaskRefs(request.workspaceId, taskRefs)) });
|
|
306
|
+
}
|
|
263
307
|
/** Return only this Workspace's current Member projection to the Client. */
|
|
264
308
|
membersForClient(request) {
|
|
265
309
|
this.requireWorkspace(request.workspaceId);
|
|
@@ -366,13 +410,7 @@ let AgentTeam = (() => {
|
|
|
366
410
|
const result = await this.requireLedger().suspendMember({ ...request, actor: agentTeamHumanActor() });
|
|
367
411
|
if (result.committed)
|
|
368
412
|
this.emitCommitted(result.value.receipt);
|
|
369
|
-
|
|
370
|
-
if (handle !== undefined) {
|
|
371
|
-
await handle.dispose();
|
|
372
|
-
this.handles.delete(request.memberId);
|
|
373
|
-
}
|
|
374
|
-
this.diagnostics.delete(request.memberId);
|
|
375
|
-
this.clearMemberNotificationState(request.memberId);
|
|
413
|
+
await this.disposeMemberSession(request.memberId, result.value.member);
|
|
376
414
|
return Object.freeze({ receipt: result.value.receipt, status: this.memberStatus(result.value.member) });
|
|
377
415
|
});
|
|
378
416
|
}
|
|
@@ -397,54 +435,19 @@ let AgentTeam = (() => {
|
|
|
397
435
|
const member = this.requireLedger().getMember(request.memberId);
|
|
398
436
|
if (member === undefined || member.workspaceId !== request.workspaceId)
|
|
399
437
|
throw new Error(`unknown Member '${request.memberId}' in workspace '${request.workspaceId}'`);
|
|
400
|
-
const storedError = this.storedErrorFor(member.memberId);
|
|
401
438
|
this.recovery.stopTracking(request.memberId);
|
|
402
|
-
|
|
439
|
+
// An orphaned composition cannot be steered: its tools are gone, so a
|
|
440
|
+
// continuation prompt reaches an inert Member. Rebuild the Agent in place.
|
|
441
|
+
const handle = this.handles.get(request.memberId);
|
|
442
|
+
if (handle !== undefined && this.ctx.agentPresets.composedPreset(handle.agent.ctx) === undefined) {
|
|
443
|
+
this.ctx.logger.info(`agent-team: rebuilding member '${member.handle}' after its preset composition was orphaned by a reload`);
|
|
444
|
+
await this.reactivateMember(request.memberId);
|
|
445
|
+
return Object.freeze({ status: this.memberStatus(member) });
|
|
446
|
+
}
|
|
403
447
|
this.ctx.logger.info(`agent-team: operator asked member '${member.handle}' to resume`);
|
|
404
|
-
this.steerResume(member, this.
|
|
448
|
+
this.steerResume(member, this.manualResumeText());
|
|
405
449
|
return Object.freeze({ status: this.memberStatus(member) });
|
|
406
450
|
}
|
|
407
|
-
/**
|
|
408
|
-
* Restart one enabled Member's live session in place: dispose the handle and
|
|
409
|
-
* reactivate on the same session id, so the transcript, private memory, and
|
|
410
|
-
* every ledger fact survive while tools and prompt templates are re-minted
|
|
411
|
-
* from the running plugin. The audit operation changes no projection state.
|
|
412
|
-
*/
|
|
413
|
-
async restartMember(request) {
|
|
414
|
-
return this.enqueueLifecycle(async () => {
|
|
415
|
-
this.requireAccepting();
|
|
416
|
-
this.requireWorkspace(request.workspaceId);
|
|
417
|
-
const stored = this.requireLedger().getMember(request.memberId);
|
|
418
|
-
if (stored === undefined || stored.workspaceId !== request.workspaceId)
|
|
419
|
-
throw new Error(`unknown Member '${request.memberId}' in workspace '${request.workspaceId}'`);
|
|
420
|
-
if (stored.state !== 'enabled')
|
|
421
|
-
throw new Error(`Agent Member '${stored.handle}' is ${stored.state}; only enabled Members can be restarted`);
|
|
422
|
-
const active = this.handles.get(request.memberId);
|
|
423
|
-
if (active === undefined)
|
|
424
|
-
throw new Error(`Agent Member '${stored.handle}' has no active session to restart`);
|
|
425
|
-
if (this.runningAgents.has(active.agent.id))
|
|
426
|
-
throw new Error(`Agent Member '${stored.handle}' is still running; wait for the current turn to end before restarting`);
|
|
427
|
-
const result = await this.requireLedger().restartMember({ ...request, actor: agentTeamHumanActor() });
|
|
428
|
-
if (result.committed)
|
|
429
|
-
this.emitCommitted(result.value.receipt);
|
|
430
|
-
// Drop the old handle's transient state: pending recovery episodes and
|
|
431
|
-
// error markers belong to the disposed agent, not to the Member.
|
|
432
|
-
this.recovery.stopTracking(request.memberId);
|
|
433
|
-
this.runtimeErrors.delete(active.agent.id);
|
|
434
|
-
this.notifiedInbox.delete(request.memberId);
|
|
435
|
-
await active.dispose();
|
|
436
|
-
this.handles.delete(request.memberId);
|
|
437
|
-
this.diagnostics.delete(request.memberId);
|
|
438
|
-
await this.activateMember(stored);
|
|
439
|
-
const reactivated = this.handles.get(request.memberId);
|
|
440
|
-
if (reactivated === undefined) {
|
|
441
|
-
// Reactivation failed; the activation diagnostic carries the reason and
|
|
442
|
-
// the audit record stays honest about the attempt.
|
|
443
|
-
throw new Error(`Agent Member '${stored.handle}' failed to reactivate: ${this.diagnostics.get(request.memberId) ?? 'unknown error'}`);
|
|
444
|
-
}
|
|
445
|
-
return Object.freeze({ receipt: result.value.receipt, status: this.memberStatus(stored) });
|
|
446
|
-
});
|
|
447
|
-
}
|
|
448
451
|
/** Ledger handle for log lines; falls back to the raw id when unknown. */
|
|
449
452
|
memberLabel(memberId) {
|
|
450
453
|
return this.ledger?.getMember(memberId)?.handle ?? memberId;
|
|
@@ -463,26 +466,11 @@ let AgentTeam = (() => {
|
|
|
463
466
|
this.attachmentGcTimer = setInterval(() => { void sweep(); }, 24 * 60 * 60 * 1000);
|
|
464
467
|
this.attachmentGcTimer.unref?.();
|
|
465
468
|
}
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
const handle = this.handles.get(memberId);
|
|
469
|
-
if (handle === undefined)
|
|
470
|
-
return undefined;
|
|
471
|
-
return this.runtimeErrors.get(handle.agent.id);
|
|
469
|
+
automaticResumeText() {
|
|
470
|
+
return 'Your previous turn ended early due to a temporary service error. Please continue the work you were doing before the error.';
|
|
472
471
|
}
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
* annotate automatic attempts, and on the final attempt ask for a graceful
|
|
476
|
-
* handoff instead of another crash.
|
|
477
|
-
*/
|
|
478
|
-
resumeText(kindLabel, options) {
|
|
479
|
-
const opening = options?.operatorRequested === true ? 'The operator asked you to resume. ' : '';
|
|
480
|
-
const attemptNote = options?.attempt === undefined ? '' : ` (automatic recovery, attempt ${options.attempt}/${RECOVERY_MAX_ATTEMPTS})`;
|
|
481
|
-
let text = `${opening}Your previous turn ended early due to an error (${kindLabel})${attemptNote}. Please continue your work from where it stopped.`;
|
|
482
|
-
if (options?.attempt === RECOVERY_MAX_ATTEMPTS) {
|
|
483
|
-
text += ' If the same error recurs, stop and leave a short handoff note for the operator.';
|
|
484
|
-
}
|
|
485
|
-
return text;
|
|
472
|
+
manualResumeText() {
|
|
473
|
+
return 'The operator asked you to resume after the previous turn ended early. Please continue the work you were doing before the error.';
|
|
486
474
|
}
|
|
487
475
|
/**
|
|
488
476
|
* Steer one continuation message into a Member's live session. Throws when
|
|
@@ -494,27 +482,31 @@ let AgentTeam = (() => {
|
|
|
494
482
|
if (handle === undefined)
|
|
495
483
|
throw new Error(`member '${member.handle}' has no active session`);
|
|
496
484
|
const notifications = this.requireLedger().notificationFacts(member.memberId, { workspaceId: member.workspaceId });
|
|
497
|
-
const body = notifications.length === 0 ? text : `${text}\n\n${this.notificationText(notifications)}`;
|
|
485
|
+
const body = notifications.length === 0 ? text : `${text}\n\n${this.notificationText(notifications, member.memberId)}`;
|
|
498
486
|
const hint = createUserMessage({
|
|
499
487
|
content: [{ type: 'text', text: body }],
|
|
500
|
-
source: { kind: 'plugin', plugin:
|
|
488
|
+
source: { kind: 'plugin', plugin: AGENT_TEAM_PLUGIN_ID, form: 'notice', summary: RECOVERY_NOTICE_SUMMARY },
|
|
501
489
|
});
|
|
490
|
+
for (const pending of [...handle.agent.inbox.nextStep, ...handle.agent.inbox.nextTurn]) {
|
|
491
|
+
if (this.isInboxNotice(pending))
|
|
492
|
+
handle.agent.inbox.remove(pending.id);
|
|
493
|
+
}
|
|
502
494
|
handle.agent.steer(hint);
|
|
503
495
|
}
|
|
504
|
-
/** Automatic-recovery
|
|
505
|
-
injectRecovery(memberId
|
|
496
|
+
/** Automatic-recovery wakeup; throwing tells the coordinator the target is gone. */
|
|
497
|
+
injectRecovery(memberId) {
|
|
506
498
|
const handle = this.handles.get(memberId);
|
|
507
499
|
const agent = handle?.agent;
|
|
508
500
|
const member = agent !== undefined ? this.memberForAgent(agent) : undefined;
|
|
509
501
|
if (agent === undefined || member === undefined || member.state !== 'enabled')
|
|
510
502
|
throw new Error(`member '${memberId}' cannot be recovered automatically`);
|
|
511
|
-
this.steerResume(member, this.
|
|
503
|
+
this.steerResume(member, this.automaticResumeText());
|
|
512
504
|
}
|
|
513
505
|
/**
|
|
514
|
-
* Human edit of one Member's mutable facts. A model
|
|
515
|
-
*
|
|
516
|
-
*
|
|
517
|
-
*
|
|
506
|
+
* Human edit of one Member's mutable facts. A live model selection is
|
|
507
|
+
* updated in place: disposing an Agent emits session/disposed, which makes
|
|
508
|
+
* the Web Client permanently mark the same Session id unavailable even when
|
|
509
|
+
* Team immediately recreates it.
|
|
518
510
|
*/
|
|
519
511
|
async updateMember(request) {
|
|
520
512
|
return this.enqueueLifecycle(async () => {
|
|
@@ -526,9 +518,10 @@ let AgentTeam = (() => {
|
|
|
526
518
|
const stored = result.value.member;
|
|
527
519
|
const active = this.handles.get(request.memberId);
|
|
528
520
|
if (active !== undefined && !isDeepStrictEqual(previous?.model ?? undefined, stored.model ?? undefined)) {
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
521
|
+
const selection = this.modelSelections.get(request.memberId);
|
|
522
|
+
if (selection === undefined)
|
|
523
|
+
throw new Error(`Agent Member '${stored.handle}' has no live model selection`);
|
|
524
|
+
selection.current = stored.model ?? this.ctx.agentDefaultModel.currentSelection();
|
|
532
525
|
}
|
|
533
526
|
return Object.freeze({ receipt: result.value.receipt, status: this.memberStatus(stored) });
|
|
534
527
|
});
|
|
@@ -539,13 +532,7 @@ let AgentTeam = (() => {
|
|
|
539
532
|
const result = await this.requireLedger().removeMember({ ...request, actor: agentTeamHumanActor() });
|
|
540
533
|
if (result.committed)
|
|
541
534
|
this.emitCommitted(result.value.receipt);
|
|
542
|
-
|
|
543
|
-
if (handle !== undefined) {
|
|
544
|
-
await handle.dispose();
|
|
545
|
-
this.handles.delete(request.memberId);
|
|
546
|
-
}
|
|
547
|
-
this.diagnostics.delete(request.memberId);
|
|
548
|
-
this.clearMemberNotificationState(request.memberId);
|
|
535
|
+
await this.disposeMemberSession(request.memberId, result.value.member);
|
|
549
536
|
await this.cleanupRemovedMember(result.value.member);
|
|
550
537
|
return result.value;
|
|
551
538
|
});
|
|
@@ -560,37 +547,26 @@ let AgentTeam = (() => {
|
|
|
560
547
|
}
|
|
561
548
|
/** Human-only Channel membership grant; it never injects historical Thread bodies. */
|
|
562
549
|
async joinChannel(request) {
|
|
563
|
-
this.
|
|
564
|
-
this.requireWorkspace(request.workspaceId);
|
|
550
|
+
const actor = this.humanCall(request.workspaceId);
|
|
565
551
|
const ledger = this.requireLedger();
|
|
566
552
|
if (!ledger.hasCommitted(request.requestId))
|
|
567
553
|
this.assertChannelMembersAvailable([request.memberId]);
|
|
568
|
-
const result = await ledger.joinChannel({ ...request, actor
|
|
554
|
+
const result = await ledger.joinChannel({ ...request, actor });
|
|
569
555
|
if (result.committed)
|
|
570
556
|
this.emitCommitted(result.value.receipt);
|
|
571
557
|
return result.value;
|
|
572
558
|
}
|
|
573
559
|
/** Human-only Channel membership removal and Channel-scoped cleanup. */
|
|
574
560
|
async removeChannelMember(request) {
|
|
575
|
-
this.
|
|
576
|
-
this.
|
|
577
|
-
const result = await this.requireLedger().removeChannelMember({ ...request, actor: agentTeamHumanActor() });
|
|
561
|
+
const actor = this.humanCall(request.workspaceId);
|
|
562
|
+
const result = await this.requireLedger().removeChannelMember({ ...request, actor });
|
|
578
563
|
if (result.committed)
|
|
579
564
|
this.emitCommitted(result.value.receipt);
|
|
580
565
|
return result.value;
|
|
581
566
|
}
|
|
582
567
|
/** Human top-level Task creation; unfollowed Agent mentions use the two-send result. */
|
|
583
568
|
async sendMessage(request) {
|
|
584
|
-
this.
|
|
585
|
-
this.requireWorkspace(request.workspaceId);
|
|
586
|
-
const metadata = await this.resolveMessageAttachments(request);
|
|
587
|
-
const result = await this.requireLedger().sendMessage({
|
|
588
|
-
...request, body: this.appendAttachmentLines(request.body, metadata),
|
|
589
|
-
...(metadata.length === 0 ? {} : { resolvedAttachments: metadata }),
|
|
590
|
-
actor: agentTeamHumanActor(),
|
|
591
|
-
});
|
|
592
|
-
this.emitCommittedOutcome(result);
|
|
593
|
-
return result.value;
|
|
569
|
+
return this.sendMessageAs(this.humanCall(request.workspaceId), request);
|
|
594
570
|
}
|
|
595
571
|
/**
|
|
596
572
|
* Resolve uploaded ids and agent-supplied absolute paths into one attachment
|
|
@@ -628,7 +604,7 @@ let AgentTeam = (() => {
|
|
|
628
604
|
const trimmed = body.trim();
|
|
629
605
|
if (metadata.length === 0)
|
|
630
606
|
return trimmed;
|
|
631
|
-
const lines = metadata.map(attachment => `[attachment] ${
|
|
607
|
+
const lines = metadata.map(attachment => `[attachment] ${attachmentPayloadPath(attachment.attachmentId, attachment.name)}`);
|
|
632
608
|
return `${trimmed}\n${lines.join('\n')}`;
|
|
633
609
|
}
|
|
634
610
|
/** Upload one composer attachment into the cache; bytes are immutable once written. */
|
|
@@ -653,22 +629,12 @@ let AgentTeam = (() => {
|
|
|
653
629
|
}
|
|
654
630
|
/** Human existing-Thread reply; unread and revision conflicts are business outcomes. */
|
|
655
631
|
async reply(request) {
|
|
656
|
-
this.
|
|
657
|
-
this.requireWorkspace(request.workspaceId);
|
|
658
|
-
const metadata = await this.resolveMessageAttachments(request);
|
|
659
|
-
const result = await this.requireLedger().reply({
|
|
660
|
-
...request, body: this.appendAttachmentLines(request.body, metadata),
|
|
661
|
-
...(metadata.length === 0 ? {} : { resolvedAttachments: metadata }),
|
|
662
|
-
actor: agentTeamHumanActor(),
|
|
663
|
-
});
|
|
664
|
-
this.emitCommittedOutcome(result);
|
|
665
|
-
return result.value;
|
|
632
|
+
return this.replyAs(this.humanCall(request.workspaceId), request);
|
|
666
633
|
}
|
|
667
634
|
/** Human's personal Attention operation. */
|
|
668
635
|
async changeAttention(request) {
|
|
669
|
-
this.
|
|
670
|
-
this.
|
|
671
|
-
const result = await this.requireLedger().changeAttention({ ...request, actor: agentTeamHumanActor() });
|
|
636
|
+
const actor = this.humanCall(request.workspaceId);
|
|
637
|
+
const result = await this.requireLedger().changeAttention({ ...request, actor });
|
|
672
638
|
if (result.committed)
|
|
673
639
|
this.emitCommitted(result.value.receipt);
|
|
674
640
|
return result.value;
|
|
@@ -680,9 +646,8 @@ let AgentTeam = (() => {
|
|
|
680
646
|
}
|
|
681
647
|
/** Human's durable, atomically acknowledged Thread read. */
|
|
682
648
|
async readThread(request) {
|
|
683
|
-
this.
|
|
684
|
-
this.
|
|
685
|
-
const result = await this.requireLedger().readThread({ ...request, actor: agentTeamHumanActor() });
|
|
649
|
+
const actor = this.humanCall(request.workspaceId);
|
|
650
|
+
const result = await this.requireLedger().readThread({ ...request, actor });
|
|
686
651
|
if (result.committed)
|
|
687
652
|
this.emitCommitted(result.value.receipt);
|
|
688
653
|
return result.value;
|
|
@@ -704,37 +669,15 @@ let AgentTeam = (() => {
|
|
|
704
669
|
}
|
|
705
670
|
/** Agent-only top-level Task creation. Workspace identity is verified against the live binding. */
|
|
706
671
|
async sendMessageForAgent(agent, request) {
|
|
707
|
-
this.
|
|
708
|
-
const actor = this.memberActor(agent);
|
|
709
|
-
this.requireAgentWorkspace(actor, request.workspaceId);
|
|
710
|
-
const metadata = await this.resolveMessageAttachments(request);
|
|
711
|
-
const result = await this.requireLedger().sendMessage({
|
|
712
|
-
...request, body: this.appendAttachmentLines(request.body, metadata),
|
|
713
|
-
...(metadata.length === 0 ? {} : { resolvedAttachments: metadata }),
|
|
714
|
-
actor,
|
|
715
|
-
});
|
|
716
|
-
this.emitCommittedOutcome(result);
|
|
717
|
-
return result.value;
|
|
672
|
+
return this.sendMessageAs(this.memberCall(agent, request.workspaceId), request);
|
|
718
673
|
}
|
|
719
674
|
/** Agent-only existing-Thread reply. */
|
|
720
675
|
async replyForAgent(agent, request) {
|
|
721
|
-
this.
|
|
722
|
-
const actor = this.memberActor(agent);
|
|
723
|
-
this.requireAgentWorkspace(actor, request.workspaceId);
|
|
724
|
-
const metadata = await this.resolveMessageAttachments(request);
|
|
725
|
-
const result = await this.requireLedger().reply({
|
|
726
|
-
...request, body: this.appendAttachmentLines(request.body, metadata),
|
|
727
|
-
...(metadata.length === 0 ? {} : { resolvedAttachments: metadata }),
|
|
728
|
-
actor,
|
|
729
|
-
});
|
|
730
|
-
this.emitCommittedOutcome(result);
|
|
731
|
-
return result.value;
|
|
676
|
+
return this.replyAs(this.memberCall(agent, request.workspaceId), request);
|
|
732
677
|
}
|
|
733
678
|
/** Agent-only personal Attention change. */
|
|
734
679
|
async changeAttentionForAgent(agent, request) {
|
|
735
|
-
this.
|
|
736
|
-
const actor = this.memberActor(agent);
|
|
737
|
-
this.requireAgentWorkspace(actor, request.workspaceId);
|
|
680
|
+
const actor = this.memberCall(agent, request.workspaceId);
|
|
738
681
|
const result = await this.requireLedger().changeAttention({ ...request, actor });
|
|
739
682
|
if (result.committed)
|
|
740
683
|
this.emitCommitted(result.value.receipt);
|
|
@@ -747,9 +690,7 @@ let AgentTeam = (() => {
|
|
|
747
690
|
}
|
|
748
691
|
/** Agent-only Claim mutation. */
|
|
749
692
|
async changeClaimForAgent(agent, request) {
|
|
750
|
-
this.
|
|
751
|
-
const actor = this.memberActor(agent);
|
|
752
|
-
this.requireAgentWorkspace(actor, request.workspaceId);
|
|
693
|
+
const actor = this.memberCall(agent, request.workspaceId);
|
|
753
694
|
const result = await this.requireLedger().changeClaim({ ...request, actor });
|
|
754
695
|
this.emitCommittedOutcome(result);
|
|
755
696
|
return result.value;
|
|
@@ -765,9 +706,7 @@ let AgentTeam = (() => {
|
|
|
765
706
|
return this.requireLedger().inbox(actor, request);
|
|
766
707
|
}
|
|
767
708
|
async readThreadForAgent(agent, request) {
|
|
768
|
-
this.
|
|
769
|
-
const actor = this.memberActor(agent);
|
|
770
|
-
this.requireAgentWorkspace(actor, request.workspaceId);
|
|
709
|
+
const actor = this.memberCall(agent, request.workspaceId);
|
|
771
710
|
const result = await this.requireLedger().readThread({ ...request, actor });
|
|
772
711
|
if (result.committed)
|
|
773
712
|
this.emitCommitted(result.value.receipt);
|
|
@@ -804,6 +743,41 @@ let AgentTeam = (() => {
|
|
|
804
743
|
throw new Error(`Agent Member '${memberId}' is not available for Channel membership`);
|
|
805
744
|
}
|
|
806
745
|
}
|
|
746
|
+
/** Shared Task-creation commit: resolve uploads into metadata lines and append through the ledger. */
|
|
747
|
+
async sendMessageAs(actor, request) {
|
|
748
|
+
const metadata = await this.resolveMessageAttachments(request);
|
|
749
|
+
const result = await this.requireLedger().sendMessage({
|
|
750
|
+
...request, body: this.appendAttachmentLines(request.body, metadata),
|
|
751
|
+
...(metadata.length === 0 ? {} : { resolvedAttachments: metadata }),
|
|
752
|
+
actor,
|
|
753
|
+
});
|
|
754
|
+
this.emitCommittedOutcome(result);
|
|
755
|
+
return result.value;
|
|
756
|
+
}
|
|
757
|
+
/** Shared existing-Thread reply commit: same upload resolution and outcome emission. */
|
|
758
|
+
async replyAs(actor, request) {
|
|
759
|
+
const metadata = await this.resolveMessageAttachments(request);
|
|
760
|
+
const result = await this.requireLedger().reply({
|
|
761
|
+
...request, body: this.appendAttachmentLines(request.body, metadata),
|
|
762
|
+
...(metadata.length === 0 ? {} : { resolvedAttachments: metadata }),
|
|
763
|
+
actor,
|
|
764
|
+
});
|
|
765
|
+
this.emitCommittedOutcome(result);
|
|
766
|
+
return result.value;
|
|
767
|
+
}
|
|
768
|
+
/** Fence one Human Remote call: accepting Host, known Workspace, Human actor. */
|
|
769
|
+
humanCall(workspaceId) {
|
|
770
|
+
this.requireAccepting();
|
|
771
|
+
this.requireWorkspace(workspaceId);
|
|
772
|
+
return agentTeamHumanActor();
|
|
773
|
+
}
|
|
774
|
+
/** Fence one Member call: accepting Host, live Member binding, matching Workspace. */
|
|
775
|
+
memberCall(agent, workspaceId) {
|
|
776
|
+
this.requireAccepting();
|
|
777
|
+
const actor = this.memberActor(agent);
|
|
778
|
+
this.requireAgentWorkspace(actor, workspaceId);
|
|
779
|
+
return actor;
|
|
780
|
+
}
|
|
807
781
|
memberActor(agent) {
|
|
808
782
|
const member = this.memberForAgent(agent);
|
|
809
783
|
if (member === undefined)
|
|
@@ -844,9 +818,16 @@ let AgentTeam = (() => {
|
|
|
844
818
|
await this.initializePrivateMemory(member.privateMemoryPath);
|
|
845
819
|
const persisted = knownSessions !== undefined ? knownSessions.has(member.sessionId)
|
|
846
820
|
: (await this.ctx.sessionPersistence.list()).some(header => header.id === member.sessionId);
|
|
821
|
+
// AgentOptions declares only provider/model. Install the full selection
|
|
822
|
+
// through the public Agent model-selection seam so reasoning effort is
|
|
823
|
+
// applied to the next request and not lost during activation.
|
|
824
|
+
const selection = member.model ?? this.ctx.agentDefaultModel.currentSelection();
|
|
825
|
+
const agentOptions = { provider: selection.provider, model: selection.model };
|
|
826
|
+
const selected = { current: selection, assembled: undefined };
|
|
847
827
|
const setup = async (agentCtx) => {
|
|
848
828
|
await this.ctx.agentPresets.mount(agentCtx, member.presetId);
|
|
849
829
|
this.validateMemberPreset(agentCtx);
|
|
830
|
+
installModelSelection(agentCtx, selected);
|
|
850
831
|
return {
|
|
851
832
|
commit: () => {
|
|
852
833
|
const agent = agentCtx.agent;
|
|
@@ -857,10 +838,6 @@ let AgentTeam = (() => {
|
|
|
857
838
|
},
|
|
858
839
|
};
|
|
859
840
|
};
|
|
860
|
-
// A Member-pinned selection wins; otherwise the Host default applies at
|
|
861
|
-
// every activation, so an override survives restarts and a cleared one
|
|
862
|
-
// re-reads the live default.
|
|
863
|
-
const agentOptions = member.model ?? this.ctx.agentDefaultModel.currentSelection();
|
|
864
841
|
created = persisted
|
|
865
842
|
? await this.ctx.agents.resume({ resumeSessionId: member.sessionId, agentOptions, setup })
|
|
866
843
|
: await this.ctx.agents.create({
|
|
@@ -871,19 +848,51 @@ let AgentTeam = (() => {
|
|
|
871
848
|
});
|
|
872
849
|
await workspace.attachSession(member.sessionId);
|
|
873
850
|
this.handles.set(member.memberId, created);
|
|
874
|
-
this.
|
|
851
|
+
this.modelSelections.set(member.memberId, selected);
|
|
852
|
+
this.clearMemberFailure(member.memberId, 'activation');
|
|
875
853
|
this.nameMemberSession(member, created.agent);
|
|
876
854
|
this.notifyMember(created.agent);
|
|
855
|
+
this.autoCompaction.activated(member.memberId);
|
|
877
856
|
}
|
|
878
857
|
catch (error) {
|
|
879
858
|
await created?.dispose();
|
|
880
|
-
this.
|
|
859
|
+
this.modelSelections.delete(member.memberId);
|
|
860
|
+
this.setMemberFailure(member.memberId, 'activation', error instanceof Error ? error.message : String(error));
|
|
881
861
|
}
|
|
882
862
|
finally {
|
|
883
863
|
// Activation only changes this Workspace's presence projection.
|
|
884
864
|
this.emitChanged([{ kind: 'workspace', workspaceId: member.workspaceId }]);
|
|
885
865
|
}
|
|
886
866
|
}
|
|
867
|
+
/**
|
|
868
|
+
* Rebuild one enabled Member in place from its persisted Session.
|
|
869
|
+
*
|
|
870
|
+
* A bundle-row reload tears down the preset roster subtree, which prunes the
|
|
871
|
+
* standing mount while live agents keep their dead scope bindings: the
|
|
872
|
+
* Member keeps its session but loses its composed tools and services.
|
|
873
|
+
* Re-running the preset composition requires a fresh Agent, and disposal is
|
|
874
|
+
* the cost — the Web Client marks the recreated Session unavailable until it
|
|
875
|
+
* is reopened, the same trade the shipped suspend/resume cycle makes.
|
|
876
|
+
*/
|
|
877
|
+
reactivateMember(memberId) {
|
|
878
|
+
return this.enqueueLifecycle(async () => {
|
|
879
|
+
const member = this.requireLedger().getMember(memberId);
|
|
880
|
+
if (member === undefined || member.state !== 'enabled')
|
|
881
|
+
return false;
|
|
882
|
+
const stale = this.handles.get(memberId);
|
|
883
|
+
if (stale !== undefined) {
|
|
884
|
+
this.handles.delete(memberId);
|
|
885
|
+
this.modelSelections.delete(memberId);
|
|
886
|
+
// The composition-loss diagnostic this heal answers is stale once the
|
|
887
|
+
// rebuild starts; a later activation must not resurface it.
|
|
888
|
+
this.clearMemberFailure(memberId, 'compaction');
|
|
889
|
+
this.emitAutoCompactionChanged(memberId);
|
|
890
|
+
await stale.dispose();
|
|
891
|
+
}
|
|
892
|
+
await this.activateMember(member);
|
|
893
|
+
return this.handles.has(memberId);
|
|
894
|
+
});
|
|
895
|
+
}
|
|
887
896
|
/**
|
|
888
897
|
* Default an untitled Member Session to its handle so the ordinary Session
|
|
889
898
|
* list names it. An explicit rename or any earlier title always wins; the
|
|
@@ -938,17 +947,35 @@ let AgentTeam = (() => {
|
|
|
938
947
|
return Object.freeze({ member, availability: 'inactive', presence: 'unavailable' });
|
|
939
948
|
if (member.state === 'suspended')
|
|
940
949
|
return Object.freeze({ member, availability: 'suspended', presence: 'unavailable' });
|
|
941
|
-
const
|
|
942
|
-
if (
|
|
943
|
-
return Object.freeze({ member, availability: 'unavailable', presence: 'unavailable', diagnostic });
|
|
950
|
+
const failures = this.memberFailures.get(member.memberId);
|
|
951
|
+
if (failures?.activation !== undefined)
|
|
952
|
+
return Object.freeze({ member, availability: 'unavailable', presence: 'unavailable', diagnostic: failures.activation });
|
|
944
953
|
const handle = this.handles.get(member.memberId);
|
|
945
954
|
if (handle === undefined)
|
|
946
955
|
return Object.freeze({ member, availability: 'unavailable', presence: 'unavailable' });
|
|
947
|
-
|
|
956
|
+
if (this.ctx.agentPresets.composedPreset(handle.agent.ctx) === undefined) {
|
|
957
|
+
return Object.freeze({ member, availability: 'active', presence: 'error', diagnostic: ORPHANED_MEMBER_DIAGNOSTIC });
|
|
958
|
+
}
|
|
959
|
+
const runtimeError = failures?.runtime ?? failures?.compaction;
|
|
948
960
|
if (runtimeError !== undefined)
|
|
949
961
|
return Object.freeze({ member, availability: 'active', presence: 'error', diagnostic: runtimeError });
|
|
950
962
|
return Object.freeze({ member, availability: 'active', presence: handle.agent.status === 'running' ? 'working' : 'available' });
|
|
951
963
|
}
|
|
964
|
+
setMemberFailure(memberId, slot, message) {
|
|
965
|
+
const failures = this.memberFailures.get(memberId) ?? {};
|
|
966
|
+
failures[slot] = message;
|
|
967
|
+
this.memberFailures.set(memberId, failures);
|
|
968
|
+
}
|
|
969
|
+
clearMemberFailure(memberId, slot) {
|
|
970
|
+
const failures = this.memberFailures.get(memberId);
|
|
971
|
+
if (failures === undefined || failures[slot] === undefined)
|
|
972
|
+
return false;
|
|
973
|
+
if (Object.keys(failures).length === 1)
|
|
974
|
+
this.memberFailures.delete(memberId);
|
|
975
|
+
else
|
|
976
|
+
delete failures[slot];
|
|
977
|
+
return true;
|
|
978
|
+
}
|
|
952
979
|
requireWorkspace(workspaceId) {
|
|
953
980
|
const workspace = this.ctx.workspaceRegistry.get(workspaceId);
|
|
954
981
|
if (workspace === undefined)
|
|
@@ -978,6 +1005,13 @@ let AgentTeam = (() => {
|
|
|
978
1005
|
if (handle !== undefined)
|
|
979
1006
|
this.notifyMember(handle.agent);
|
|
980
1007
|
}
|
|
1008
|
+
const compactionMembers = acceptedTaskCompactionMembers(operation);
|
|
1009
|
+
if (compactionMembers !== undefined)
|
|
1010
|
+
this.autoCompaction.schedule(compactionMembers);
|
|
1011
|
+
}
|
|
1012
|
+
emitAutoCompactionChanged(memberId) {
|
|
1013
|
+
const workspaceId = this.ledger?.getMember(memberId)?.workspaceId;
|
|
1014
|
+
this.emitChanged(workspaceId === undefined ? undefined : [{ kind: 'workspace', workspaceId }]);
|
|
981
1015
|
}
|
|
982
1016
|
/** Wake from durable unread state with bounded facts for direct and state-changing work. */
|
|
983
1017
|
notifyMember(agent) {
|
|
@@ -994,12 +1028,19 @@ let AgentTeam = (() => {
|
|
|
994
1028
|
]));
|
|
995
1029
|
if (this.notifiedInbox.get(member.memberId) === signature)
|
|
996
1030
|
return;
|
|
997
|
-
const
|
|
998
|
-
|
|
999
|
-
|
|
1031
|
+
const pending = [...agent.inbox.nextStep, ...agent.inbox.nextTurn];
|
|
1032
|
+
// steerResume already combines the recovery instruction and these durable
|
|
1033
|
+
// facts. Its synchronous running transition must not replace that notice.
|
|
1034
|
+
if (pending.some(message => this.isRecoveryNotice(message))) {
|
|
1035
|
+
this.notifiedInbox.set(member.memberId, signature);
|
|
1036
|
+
return;
|
|
1037
|
+
}
|
|
1038
|
+
const existingInboxHint = pending.find(message => this.isInboxNotice(message));
|
|
1039
|
+
if (existingInboxHint !== undefined)
|
|
1040
|
+
agent.inbox.remove(existingInboxHint.id);
|
|
1000
1041
|
const hint = createUserMessage({
|
|
1001
|
-
content: [{ type: 'text', text: this.notificationText(notifications) }],
|
|
1002
|
-
source: { kind: 'plugin', plugin:
|
|
1042
|
+
content: [{ type: 'text', text: this.notificationText(notifications, member.memberId) }],
|
|
1043
|
+
source: { kind: 'plugin', plugin: AGENT_TEAM_PLUGIN_ID, form: 'notice', summary: INBOX_NOTICE_SUMMARY },
|
|
1003
1044
|
});
|
|
1004
1045
|
this.notifiedInbox.set(member.memberId, signature);
|
|
1005
1046
|
try {
|
|
@@ -1010,7 +1051,17 @@ let AgentTeam = (() => {
|
|
|
1010
1051
|
throw error;
|
|
1011
1052
|
}
|
|
1012
1053
|
}
|
|
1013
|
-
|
|
1054
|
+
isInboxNotice(message) {
|
|
1055
|
+
const source = message.source;
|
|
1056
|
+
return source.kind === 'plugin' && source.plugin === AGENT_TEAM_PLUGIN_ID
|
|
1057
|
+
&& source.form === 'notice' && source.summary === INBOX_NOTICE_SUMMARY;
|
|
1058
|
+
}
|
|
1059
|
+
isRecoveryNotice(message) {
|
|
1060
|
+
const source = message.source;
|
|
1061
|
+
return source.kind === 'plugin' && source.plugin === AGENT_TEAM_PLUGIN_ID
|
|
1062
|
+
&& source.form === 'notice' && source.summary === RECOVERY_NOTICE_SUMMARY;
|
|
1063
|
+
}
|
|
1064
|
+
notificationText(notifications, readerId) {
|
|
1014
1065
|
const maxCharacters = 32 * 1024;
|
|
1015
1066
|
const sections = ['Team Inbox has unread work.'];
|
|
1016
1067
|
let characterCount = sections[0].length;
|
|
@@ -1036,19 +1087,19 @@ let AgentTeam = (() => {
|
|
|
1036
1087
|
? 'human' : this.requireLedger().getMember(fact.message.sender)?.handle ?? fact.message.sender;
|
|
1037
1088
|
const detail = ['Direct Team mention', `From: ${sender}`, `Channel: ${item.channelRef}`,
|
|
1038
1089
|
`Task: ${item.task.taskRef}`, `Thread: ${item.thread.threadRef}`, `Message ref: ${fact.message.messageRef}`,
|
|
1039
|
-
`
|
|
1090
|
+
`Message: ${this.boundedNotificationBody(fact.message.body)}`].join('\n');
|
|
1040
1091
|
if (append(detail))
|
|
1041
1092
|
detailedFactCount += 1;
|
|
1042
1093
|
}
|
|
1043
1094
|
else if (fact.kind === 'activity') {
|
|
1044
|
-
const detail = `${this.activityNotification(fact.activity)}\nThread: ${item.thread.threadRef}
|
|
1095
|
+
const detail = `${this.activityNotification(fact.activity, readerId)}\nThread: ${item.thread.threadRef}`;
|
|
1045
1096
|
if (append(detail))
|
|
1046
1097
|
detailedFactCount += 1;
|
|
1047
1098
|
}
|
|
1048
1099
|
}
|
|
1049
1100
|
const ordinaryCount = facts.filter(entry => entry.fact.kind === 'message' && !entry.direct).length;
|
|
1050
1101
|
if (ordinaryCount > 0)
|
|
1051
|
-
append(`Task ${item.task.taskRef}: ${ordinaryCount} unread update${ordinaryCount === 1 ? '' : 's'}
|
|
1102
|
+
append(`Task ${item.task.taskRef}: ${ordinaryCount} unread update${ordinaryCount === 1 ? '' : 's'}.`);
|
|
1052
1103
|
}
|
|
1053
1104
|
if (omitted)
|
|
1054
1105
|
sections.push('More unread work remains in team_inbox; the automatic context is bounded.');
|
|
@@ -1059,9 +1110,15 @@ let AgentTeam = (() => {
|
|
|
1059
1110
|
const limit = 8 * 1024;
|
|
1060
1111
|
return body.length <= limit ? body : `${body.slice(0, limit)}\n[Message body truncated; use team_thread read for the full Message.]`;
|
|
1061
1112
|
}
|
|
1062
|
-
activityNotification(activity) {
|
|
1113
|
+
activityNotification(activity, readerId) {
|
|
1063
1114
|
const actor = activity.actor === AGENT_TEAM_HUMAN_MEMBER_ID
|
|
1064
1115
|
? 'human' : this.requireLedger().getMember(activity.actor)?.handle ?? activity.actor;
|
|
1116
|
+
// Early acceptance completes the reader's own open Claim inside the accept
|
|
1117
|
+
// operation: say so plainly, or the owner keeps working on a done Task.
|
|
1118
|
+
if (activity.kind === 'accept' && activity.actor === AGENT_TEAM_HUMAN_MEMBER_ID && readerId !== undefined
|
|
1119
|
+
&& activity.completedClaimRefs?.some(claimRef => this.requireLedger().getClaim(claimRef)?.owner === readerId)) {
|
|
1120
|
+
return `Team Task update\n${actor} accepted Task ${activity.taskRef} and your open Claim was completed with it. No further work is needed.`;
|
|
1121
|
+
}
|
|
1065
1122
|
if (activity.kind === 'claim' || activity.kind === 'done' || activity.kind === 'release') {
|
|
1066
1123
|
return `Team Task update\n${actor} ${activity.kind} Claim ${activity.claimRef} on Task ${activity.taskRef}.`;
|
|
1067
1124
|
}
|
|
@@ -1072,6 +1129,22 @@ let AgentTeam = (() => {
|
|
|
1072
1129
|
? ` Released Claims: ${activity.releasedClaimRefs.join(', ')}.` : '';
|
|
1073
1130
|
return `Team Task update\n${actor} ${activity.kind} Task ${activity.taskRef}.${released}`;
|
|
1074
1131
|
}
|
|
1132
|
+
/** Dispose one live Member Session and drop its per-Member runtime state. */
|
|
1133
|
+
async disposeMemberSession(memberId, member) {
|
|
1134
|
+
this.clearMemberRecoveryState(member);
|
|
1135
|
+
const handle = this.handles.get(memberId);
|
|
1136
|
+
if (handle !== undefined) {
|
|
1137
|
+
await handle.dispose();
|
|
1138
|
+
this.handles.delete(memberId);
|
|
1139
|
+
}
|
|
1140
|
+
this.modelSelections.delete(memberId);
|
|
1141
|
+
this.clearMemberFailure(memberId, 'activation');
|
|
1142
|
+
}
|
|
1143
|
+
clearMemberRecoveryState(member) {
|
|
1144
|
+
this.recovery.stopTracking(member.memberId);
|
|
1145
|
+
this.clearMemberFailure(member.memberId, 'runtime');
|
|
1146
|
+
this.clearMemberNotificationState(member.memberId);
|
|
1147
|
+
}
|
|
1075
1148
|
clearMemberNotificationState(memberId) {
|
|
1076
1149
|
this.notifiedInbox.delete(memberId);
|
|
1077
1150
|
}
|