@wowyuarm/dsh-agent-team 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -1
- package/README.zh.md +9 -1
- package/package.json +12 -3
- package/packages/agent-team/README.md +3 -3
- package/packages/agent-team/README.zh.md +3 -3
- 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 +252 -139
- package/packages/agent-team/lib/ledger.js +344 -170
- package/packages/agent-team/lib/recovery.js +30 -35
- package/packages/agent-team/lib/spec.js +27 -11
- package/packages/agent-team/lib/typert.host.js +197 -88
- package/packages/agent-team/lib/typert.remote-client.d.ts +3 -1
- package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
- package/packages/agent-team/lib/typert.remote-client.js +139 -59
- 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 +52 -19
- package/packages/agent-team/lib/types/index.d.ts.map +1 -1
- package/packages/agent-team/lib/types/ledger.d.ts +29 -2
- 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 +69 -30
- package/packages/agent-team/lib/types/types.d.ts.map +1 -1
- package/packages/agent-team/preset/team-member/agent.cordis.yml +7 -2
- package/packages/client-agent-team/README.md +2 -1
- package/packages/client-agent-team/README.zh.md +2 -1
- package/packages/client-agent-team/lib/client.js +1577 -863
- package/packages/client-agent-team/lib/client.js.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.js +35 -174
- package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamChannelPage.js +20 -30
- package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.js +3 -2
- package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts +6 -1
- package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamComposer.js +4 -5
- 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 +4 -1
- package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamMessage.js +94 -47
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts +2 -1
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.js +81 -39
- 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 -1
- 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 -1
- package/packages/client-agent-team/lib/types/client/locales.d.ts +24 -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 +24 -0
- 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/navigation.d.ts +1 -1
- package/packages/client-agent-team/lib/types/client/navigation.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/navigation.js +12 -6
- package/packages/client-agent-team/lib/types/client/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/slots.d.ts +2 -1
- package/packages/client-agent-team/lib/types/client/slots.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/task-refs.d.ts +6 -1
- package/packages/client-agent-team/lib/types/client/task-refs.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/task-refs.js +19 -0
- package/packages/client-agent-team/lib/types/client/team-formatters.d.ts +52 -3
- 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 +74 -4
- 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/README.md +7 -7
- package/packages/tool-agent-team/README.zh.md +7 -7
- package/packages/tool-agent-team/lib/index.js +38 -28
- package/packages/tool-agent-team/lib/types/index.d.ts.map +1 -1
- package/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
|
@@ -51,14 +51,19 @@ import { scopeOf } from '@deepseek-ai/dsh-scope';
|
|
|
51
51
|
import { SessionId } from '@deepseek-ai/dsh-session';
|
|
52
52
|
import { effectiveSandboxMode, setSandboxMode } from '@deepseek-ai/dsh-sandbox-policy';
|
|
53
53
|
import { Remote, TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
|
|
54
|
-
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";
|
|
55
56
|
import { AGENT_TEAM_HUMAN_MEMBER_ID, AgentTeamLedger, agentTeamHumanActor } from "./ledger.js";
|
|
56
|
-
import { classifyRecoverableError, RecoveryCoordinator,
|
|
57
|
+
import { classifyRecoverableError, RecoveryCoordinator, RECOVERY_MAX_CONSECUTIVE_ERRORS } from "./recovery.js";
|
|
57
58
|
import { agentTeamDomainSpec } from "./spec.js";
|
|
58
59
|
export { agentTeamDomainSpec, agentTeamOperationSchema } from "./spec.js";
|
|
59
60
|
export { AGENT_TEAM_HUMAN_MEMBER_ID, AGENT_TEAM_INITIALIZE_REQUEST_ID } from "./ledger.js";
|
|
60
61
|
/** Process-stable marker carried by the final Team message tool definition. */
|
|
61
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.';
|
|
62
67
|
function sameChangeScope(left, right) {
|
|
63
68
|
if (left.kind === 'workspace' && right.kind === 'workspace')
|
|
64
69
|
return left.workspaceId === right.workspaceId;
|
|
@@ -93,6 +98,7 @@ let AgentTeam = (() => {
|
|
|
93
98
|
let _recoverMember_decorators;
|
|
94
99
|
let _updateMember_decorators;
|
|
95
100
|
let _changeTask_decorators;
|
|
101
|
+
let _promoteThread_decorators;
|
|
96
102
|
let _joinChannel_decorators;
|
|
97
103
|
let _removeChannelMember_decorators;
|
|
98
104
|
let _sendMessage_decorators;
|
|
@@ -117,6 +123,7 @@ let AgentTeam = (() => {
|
|
|
117
123
|
_recoverMember_decorators = [Remote('recoverMember')];
|
|
118
124
|
_updateMember_decorators = [Remote('updateMember')];
|
|
119
125
|
_changeTask_decorators = [Remote('changeTask')];
|
|
126
|
+
_promoteThread_decorators = [Remote('promoteThread')];
|
|
120
127
|
_joinChannel_decorators = [Remote('joinChannel')];
|
|
121
128
|
_removeChannelMember_decorators = [Remote('removeChannelMember')];
|
|
122
129
|
_sendMessage_decorators = [Remote('sendMessage')];
|
|
@@ -138,6 +145,7 @@ let AgentTeam = (() => {
|
|
|
138
145
|
__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);
|
|
139
146
|
__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);
|
|
140
147
|
__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);
|
|
148
|
+
__esDecorate(this, null, _promoteThread_decorators, { kind: "method", name: "promoteThread", static: false, private: false, access: { has: obj => "promoteThread" in obj, get: obj => obj.promoteThread }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
141
149
|
__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);
|
|
142
150
|
__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);
|
|
143
151
|
__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);
|
|
@@ -166,19 +174,26 @@ let AgentTeam = (() => {
|
|
|
166
174
|
handles = new Map();
|
|
167
175
|
/** Live selection refs let model edits take effect without disposing the Session. */
|
|
168
176
|
modelSelections = new Map();
|
|
169
|
-
|
|
170
|
-
|
|
177
|
+
/**
|
|
178
|
+
* Why one Member shows error presence, per failure source. Reads prefer
|
|
179
|
+
* activation, then runtime, then compaction; slots clear independently, so
|
|
180
|
+
* a recovered runtime error re-reveals an outstanding compaction failure.
|
|
181
|
+
* Keyed by Member rather than Session so a restarted Session cannot leak
|
|
182
|
+
* stale keys.
|
|
183
|
+
*/
|
|
184
|
+
memberFailures = new Map();
|
|
185
|
+
autoCompaction;
|
|
171
186
|
/** Agent ids with a turn in flight; restarts must wait for the boundary. */
|
|
172
187
|
runningAgents = new Set();
|
|
173
188
|
notifiedInbox = new Map();
|
|
174
189
|
attachmentGcTimer;
|
|
175
190
|
recovery = new RecoveryCoordinator({
|
|
176
|
-
|
|
177
|
-
this.ctx.logger.info(`agent-team: automatic recovery
|
|
178
|
-
this.injectRecovery(memberId
|
|
191
|
+
wake: memberId => {
|
|
192
|
+
this.ctx.logger.info(`agent-team: automatic recovery wakeup for member '${this.memberLabel(memberId)}' after consecutive recoverable failures`);
|
|
193
|
+
this.injectRecovery(memberId);
|
|
179
194
|
},
|
|
180
|
-
onStandDown: (memberId,
|
|
181
|
-
this.ctx.logger.warn(`agent-team: member '${this.memberLabel(memberId)}'
|
|
195
|
+
onStandDown: (memberId, consecutiveFailures) => {
|
|
196
|
+
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`);
|
|
182
197
|
},
|
|
183
198
|
});
|
|
184
199
|
lifecycleTail = Promise.resolve();
|
|
@@ -187,6 +202,21 @@ let AgentTeam = (() => {
|
|
|
187
202
|
changeWaiters = new Set();
|
|
188
203
|
constructor(ctx) {
|
|
189
204
|
super(ctx, 'agentTeam');
|
|
205
|
+
this.autoCompaction = new AutoCompactionCoordinator({
|
|
206
|
+
agentForMember: memberId => this.handles.get(memberId)?.agent,
|
|
207
|
+
compactionForAgent: agent => this.ctx.agentPresets.serviceFor(agent, 'compaction'),
|
|
208
|
+
reactivate: memberId => this.reactivateMember(memberId),
|
|
209
|
+
failed: (memberId, _sessionId, diagnostic) => {
|
|
210
|
+
this.setMemberFailure(memberId, 'compaction', diagnostic);
|
|
211
|
+
this.emitAutoCompactionChanged(memberId);
|
|
212
|
+
},
|
|
213
|
+
cleared: memberId => {
|
|
214
|
+
if (!this.clearMemberFailure(memberId, 'compaction'))
|
|
215
|
+
return;
|
|
216
|
+
this.emitAutoCompactionChanged(memberId);
|
|
217
|
+
},
|
|
218
|
+
log: message => { this.ctx.logger.warn(`agent-team: ${message}`); },
|
|
219
|
+
});
|
|
190
220
|
}
|
|
191
221
|
/** Open the durable ledger and restore every enabled Member independently. */
|
|
192
222
|
async [Service.init]() {
|
|
@@ -195,10 +225,10 @@ let AgentTeam = (() => {
|
|
|
195
225
|
if (member === undefined)
|
|
196
226
|
return;
|
|
197
227
|
const message = error instanceof Error ? error.message : String(error);
|
|
198
|
-
this.
|
|
228
|
+
this.setMemberFailure(member.memberId, 'runtime', message);
|
|
199
229
|
const kind = classifyRecoverableError(message);
|
|
200
230
|
if (kind !== undefined)
|
|
201
|
-
this.ctx.logger.warn(`agent-team: member '${member.handle}' hit a recoverable
|
|
231
|
+
this.ctx.logger.warn(`agent-team: member '${member.handle}' hit a recoverable ${kind} error; recording a consecutive error occurrence`);
|
|
202
232
|
this.recovery.onError(member.memberId, message);
|
|
203
233
|
this.emitChanged([{ kind: 'workspace', workspaceId: member.workspaceId }]);
|
|
204
234
|
});
|
|
@@ -209,14 +239,14 @@ let AgentTeam = (() => {
|
|
|
209
239
|
else
|
|
210
240
|
this.runningAgents.delete(agent.id);
|
|
211
241
|
if (status === 'running' && member !== undefined) {
|
|
212
|
-
const recovered = this.
|
|
242
|
+
const recovered = this.clearMemberFailure(member.memberId, 'runtime');
|
|
213
243
|
if (recovered)
|
|
214
244
|
this.notifiedInbox.delete(member.memberId);
|
|
215
245
|
this.emitChanged([{ kind: 'workspace', workspaceId: member.workspaceId }]);
|
|
216
246
|
this.notifyMember(agent);
|
|
217
247
|
}
|
|
218
248
|
// A turn that ends without an error closes any automatic recovery episode.
|
|
219
|
-
if (status === 'idle' && member !== undefined &&
|
|
249
|
+
if (status === 'idle' && member !== undefined && this.memberFailures.get(member.memberId)?.runtime === undefined) {
|
|
220
250
|
this.recovery.onCleanTurnEnd(member.memberId);
|
|
221
251
|
}
|
|
222
252
|
});
|
|
@@ -224,6 +254,7 @@ let AgentTeam = (() => {
|
|
|
224
254
|
this.ctx.effect(() => async () => {
|
|
225
255
|
this.accepting = false;
|
|
226
256
|
this.recovery.dispose();
|
|
257
|
+
await this.autoCompaction.dispose();
|
|
227
258
|
if (this.attachmentGcTimer !== undefined)
|
|
228
259
|
clearInterval(this.attachmentGcTimer);
|
|
229
260
|
this.attachmentGcTimer = undefined;
|
|
@@ -264,7 +295,6 @@ let AgentTeam = (() => {
|
|
|
264
295
|
members() {
|
|
265
296
|
return this.requireLedger().listMembers().map(member => this.memberStatus(member));
|
|
266
297
|
}
|
|
267
|
-
/** Return only this Workspace's current Member projection to the Client. */
|
|
268
298
|
/** Read-only navigation lookup for branded Task refs found in message bodies. */
|
|
269
299
|
resolveTaskRefs(request) {
|
|
270
300
|
this.requireWorkspace(request.workspaceId);
|
|
@@ -277,6 +307,7 @@ let AgentTeam = (() => {
|
|
|
277
307
|
});
|
|
278
308
|
return Object.freeze({ resolved: Object.freeze(this.requireLedger().resolveTaskRefs(request.workspaceId, taskRefs)) });
|
|
279
309
|
}
|
|
310
|
+
/** Return only this Workspace's current Member projection to the Client. */
|
|
280
311
|
membersForClient(request) {
|
|
281
312
|
this.requireWorkspace(request.workspaceId);
|
|
282
313
|
return this.members()
|
|
@@ -382,14 +413,7 @@ let AgentTeam = (() => {
|
|
|
382
413
|
const result = await this.requireLedger().suspendMember({ ...request, actor: agentTeamHumanActor() });
|
|
383
414
|
if (result.committed)
|
|
384
415
|
this.emitCommitted(result.value.receipt);
|
|
385
|
-
|
|
386
|
-
if (handle !== undefined) {
|
|
387
|
-
await handle.dispose();
|
|
388
|
-
this.handles.delete(request.memberId);
|
|
389
|
-
}
|
|
390
|
-
this.modelSelections.delete(request.memberId);
|
|
391
|
-
this.diagnostics.delete(request.memberId);
|
|
392
|
-
this.clearMemberNotificationState(request.memberId);
|
|
416
|
+
await this.disposeMemberSession(request.memberId, result.value.member);
|
|
393
417
|
return Object.freeze({ receipt: result.value.receipt, status: this.memberStatus(result.value.member) });
|
|
394
418
|
});
|
|
395
419
|
}
|
|
@@ -405,20 +429,38 @@ let AgentTeam = (() => {
|
|
|
405
429
|
});
|
|
406
430
|
}
|
|
407
431
|
/**
|
|
408
|
-
* Operator nudge for
|
|
409
|
-
* into its live session
|
|
410
|
-
*
|
|
432
|
+
* Operator nudge for a Member that stopped making progress: steer a
|
|
433
|
+
* continuation prompt into its live session, rebuild it after an orphaned
|
|
434
|
+
* preset composition, or re-run activation when no live session exists.
|
|
435
|
+
* Runtime-only — no ledger operation, no suspend. Taking over manually also
|
|
436
|
+
* cancels any pending automatic recovery episode.
|
|
411
437
|
*/
|
|
412
438
|
async recoverMember(request) {
|
|
413
439
|
this.requireAccepting();
|
|
414
440
|
const member = this.requireLedger().getMember(request.memberId);
|
|
415
441
|
if (member === undefined || member.workspaceId !== request.workspaceId)
|
|
416
442
|
throw new Error(`unknown Member '${request.memberId}' in workspace '${request.workspaceId}'`);
|
|
417
|
-
const storedError = this.storedErrorFor(member.memberId);
|
|
418
443
|
this.recovery.stopTracking(request.memberId);
|
|
419
|
-
|
|
444
|
+
// An orphaned composition cannot be steered: its tools are gone, so a
|
|
445
|
+
// continuation prompt reaches an inert Member. Rebuild the Agent in place.
|
|
446
|
+
const handle = this.handles.get(request.memberId);
|
|
447
|
+
if (handle !== undefined && this.ctx.agentPresets.composedPreset(handle.agent.ctx) === undefined) {
|
|
448
|
+
this.ctx.logger.info(`agent-team: rebuilding member '${member.handle}' after its preset composition was orphaned by a reload`);
|
|
449
|
+
await this.reactivateMember(request.memberId);
|
|
450
|
+
return Object.freeze({ status: this.memberStatus(member) });
|
|
451
|
+
}
|
|
452
|
+
// A failed activation also leaves nothing to steer; re-running it is the
|
|
453
|
+
// only way back. A renewed failure stays non-throwing: the refreshed
|
|
454
|
+
// status carries the activation diagnostic for the sidebar.
|
|
455
|
+
if (handle === undefined) {
|
|
456
|
+
if (member.state !== 'enabled')
|
|
457
|
+
throw new Error(`Agent Member '${member.handle}' is ${member.state}; only enabled Members can be restarted`);
|
|
458
|
+
this.ctx.logger.info(`agent-team: restarting member '${member.handle}' after a failed activation`);
|
|
459
|
+
await this.reactivateMember(request.memberId);
|
|
460
|
+
return Object.freeze({ status: this.memberStatus(member) });
|
|
461
|
+
}
|
|
420
462
|
this.ctx.logger.info(`agent-team: operator asked member '${member.handle}' to resume`);
|
|
421
|
-
this.steerResume(member, this.
|
|
463
|
+
this.steerResume(member, this.manualResumeText());
|
|
422
464
|
return Object.freeze({ status: this.memberStatus(member) });
|
|
423
465
|
}
|
|
424
466
|
/** Ledger handle for log lines; falls back to the raw id when unknown. */
|
|
@@ -439,26 +481,11 @@ let AgentTeam = (() => {
|
|
|
439
481
|
this.attachmentGcTimer = setInterval(() => { void sweep(); }, 24 * 60 * 60 * 1000);
|
|
440
482
|
this.attachmentGcTimer.unref?.();
|
|
441
483
|
}
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
const handle = this.handles.get(memberId);
|
|
445
|
-
if (handle === undefined)
|
|
446
|
-
return undefined;
|
|
447
|
-
return this.runtimeErrors.get(handle.agent.id);
|
|
484
|
+
automaticResumeText() {
|
|
485
|
+
return 'Your previous turn ended early due to a temporary service error. Please continue the work you were doing before the error.';
|
|
448
486
|
}
|
|
449
|
-
|
|
450
|
-
|
|
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;
|
|
487
|
+
manualResumeText() {
|
|
488
|
+
return 'The operator asked you to resume after the previous turn ended early. Please continue the work you were doing before the error.';
|
|
462
489
|
}
|
|
463
490
|
/**
|
|
464
491
|
* Steer one continuation message into a Member's live session. Throws when
|
|
@@ -473,18 +500,22 @@ let AgentTeam = (() => {
|
|
|
473
500
|
const body = notifications.length === 0 ? text : `${text}\n\n${this.notificationText(notifications, member.memberId)}`;
|
|
474
501
|
const hint = createUserMessage({
|
|
475
502
|
content: [{ type: 'text', text: body }],
|
|
476
|
-
source: { kind: 'plugin', plugin:
|
|
503
|
+
source: { kind: 'plugin', plugin: AGENT_TEAM_PLUGIN_ID, form: 'notice', summary: RECOVERY_NOTICE_SUMMARY },
|
|
477
504
|
});
|
|
505
|
+
for (const pending of [...handle.agent.inbox.nextStep, ...handle.agent.inbox.nextTurn]) {
|
|
506
|
+
if (this.isInboxNotice(pending))
|
|
507
|
+
handle.agent.inbox.remove(pending.id);
|
|
508
|
+
}
|
|
478
509
|
handle.agent.steer(hint);
|
|
479
510
|
}
|
|
480
|
-
/** Automatic-recovery
|
|
481
|
-
injectRecovery(memberId
|
|
511
|
+
/** Automatic-recovery wakeup; throwing tells the coordinator the target is gone. */
|
|
512
|
+
injectRecovery(memberId) {
|
|
482
513
|
const handle = this.handles.get(memberId);
|
|
483
514
|
const agent = handle?.agent;
|
|
484
515
|
const member = agent !== undefined ? this.memberForAgent(agent) : undefined;
|
|
485
516
|
if (agent === undefined || member === undefined || member.state !== 'enabled')
|
|
486
517
|
throw new Error(`member '${memberId}' cannot be recovered automatically`);
|
|
487
|
-
this.steerResume(member, this.
|
|
518
|
+
this.steerResume(member, this.automaticResumeText());
|
|
488
519
|
}
|
|
489
520
|
/**
|
|
490
521
|
* Human edit of one Member's mutable facts. A live model selection is
|
|
@@ -516,14 +547,7 @@ let AgentTeam = (() => {
|
|
|
516
547
|
const result = await this.requireLedger().removeMember({ ...request, actor: agentTeamHumanActor() });
|
|
517
548
|
if (result.committed)
|
|
518
549
|
this.emitCommitted(result.value.receipt);
|
|
519
|
-
|
|
520
|
-
if (handle !== undefined) {
|
|
521
|
-
await handle.dispose();
|
|
522
|
-
this.handles.delete(request.memberId);
|
|
523
|
-
}
|
|
524
|
-
this.modelSelections.delete(request.memberId);
|
|
525
|
-
this.diagnostics.delete(request.memberId);
|
|
526
|
-
this.clearMemberNotificationState(request.memberId);
|
|
550
|
+
await this.disposeMemberSession(request.memberId, result.value.member);
|
|
527
551
|
await this.cleanupRemovedMember(result.value.member);
|
|
528
552
|
return result.value;
|
|
529
553
|
});
|
|
@@ -536,39 +560,36 @@ let AgentTeam = (() => {
|
|
|
536
560
|
this.emitCommittedOutcome(result);
|
|
537
561
|
return result.value;
|
|
538
562
|
}
|
|
539
|
-
/** Human-only
|
|
540
|
-
async
|
|
563
|
+
/** Human-only promotion of a taskless Thread into a real Task plus public Message. */
|
|
564
|
+
async promoteThread(request) {
|
|
541
565
|
this.requireAccepting();
|
|
542
566
|
this.requireWorkspace(request.workspaceId);
|
|
567
|
+
const result = await this.requireLedger().promoteThread({ ...request, actor: agentTeamHumanActor() });
|
|
568
|
+
this.emitCommittedOutcome(result);
|
|
569
|
+
return result.value;
|
|
570
|
+
}
|
|
571
|
+
/** Human-only Channel membership grant; it never injects historical Thread bodies. */
|
|
572
|
+
async joinChannel(request) {
|
|
573
|
+
const actor = this.humanCall(request.workspaceId);
|
|
543
574
|
const ledger = this.requireLedger();
|
|
544
575
|
if (!ledger.hasCommitted(request.requestId))
|
|
545
576
|
this.assertChannelMembersAvailable([request.memberId]);
|
|
546
|
-
const result = await ledger.joinChannel({ ...request, actor
|
|
577
|
+
const result = await ledger.joinChannel({ ...request, actor });
|
|
547
578
|
if (result.committed)
|
|
548
579
|
this.emitCommitted(result.value.receipt);
|
|
549
580
|
return result.value;
|
|
550
581
|
}
|
|
551
582
|
/** Human-only Channel membership removal and Channel-scoped cleanup. */
|
|
552
583
|
async removeChannelMember(request) {
|
|
553
|
-
this.
|
|
554
|
-
this.
|
|
555
|
-
const result = await this.requireLedger().removeChannelMember({ ...request, actor: agentTeamHumanActor() });
|
|
584
|
+
const actor = this.humanCall(request.workspaceId);
|
|
585
|
+
const result = await this.requireLedger().removeChannelMember({ ...request, actor });
|
|
556
586
|
if (result.committed)
|
|
557
587
|
this.emitCommitted(result.value.receipt);
|
|
558
588
|
return result.value;
|
|
559
589
|
}
|
|
560
|
-
/** Human top-level
|
|
590
|
+
/** Human top-level Thread start; asTask attaches an optional Task overlay. */
|
|
561
591
|
async sendMessage(request) {
|
|
562
|
-
this.
|
|
563
|
-
this.requireWorkspace(request.workspaceId);
|
|
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
|
-
});
|
|
570
|
-
this.emitCommittedOutcome(result);
|
|
571
|
-
return result.value;
|
|
592
|
+
return this.sendMessageAs(this.humanCall(request.workspaceId), request);
|
|
572
593
|
}
|
|
573
594
|
/**
|
|
574
595
|
* Resolve uploaded ids and agent-supplied absolute paths into one attachment
|
|
@@ -606,7 +627,7 @@ let AgentTeam = (() => {
|
|
|
606
627
|
const trimmed = body.trim();
|
|
607
628
|
if (metadata.length === 0)
|
|
608
629
|
return trimmed;
|
|
609
|
-
const lines = metadata.map(attachment => `[attachment] ${
|
|
630
|
+
const lines = metadata.map(attachment => `[attachment] ${attachmentPayloadPath(attachment.attachmentId, attachment.name)}`);
|
|
610
631
|
return `${trimmed}\n${lines.join('\n')}`;
|
|
611
632
|
}
|
|
612
633
|
/** Upload one composer attachment into the cache; bytes are immutable once written. */
|
|
@@ -631,22 +652,12 @@ let AgentTeam = (() => {
|
|
|
631
652
|
}
|
|
632
653
|
/** Human existing-Thread reply; unread and revision conflicts are business outcomes. */
|
|
633
654
|
async reply(request) {
|
|
634
|
-
this.
|
|
635
|
-
this.requireWorkspace(request.workspaceId);
|
|
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
|
-
});
|
|
642
|
-
this.emitCommittedOutcome(result);
|
|
643
|
-
return result.value;
|
|
655
|
+
return this.replyAs(this.humanCall(request.workspaceId), request);
|
|
644
656
|
}
|
|
645
657
|
/** Human's personal Attention operation. */
|
|
646
658
|
async changeAttention(request) {
|
|
647
|
-
this.
|
|
648
|
-
this.
|
|
649
|
-
const result = await this.requireLedger().changeAttention({ ...request, actor: agentTeamHumanActor() });
|
|
659
|
+
const actor = this.humanCall(request.workspaceId);
|
|
660
|
+
const result = await this.requireLedger().changeAttention({ ...request, actor });
|
|
650
661
|
if (result.committed)
|
|
651
662
|
this.emitCommitted(result.value.receipt);
|
|
652
663
|
return result.value;
|
|
@@ -658,9 +669,8 @@ let AgentTeam = (() => {
|
|
|
658
669
|
}
|
|
659
670
|
/** Human's durable, atomically acknowledged Thread read. */
|
|
660
671
|
async readThread(request) {
|
|
661
|
-
this.
|
|
662
|
-
this.
|
|
663
|
-
const result = await this.requireLedger().readThread({ ...request, actor: agentTeamHumanActor() });
|
|
672
|
+
const actor = this.humanCall(request.workspaceId);
|
|
673
|
+
const result = await this.requireLedger().readThread({ ...request, actor });
|
|
664
674
|
if (result.committed)
|
|
665
675
|
this.emitCommitted(result.value.receipt);
|
|
666
676
|
return result.value;
|
|
@@ -680,39 +690,17 @@ let AgentTeam = (() => {
|
|
|
680
690
|
this.requireWorkspace(request.workspaceId);
|
|
681
691
|
return this.requireLedger().view(request);
|
|
682
692
|
}
|
|
683
|
-
/** Agent-only top-level
|
|
693
|
+
/** Agent-only top-level Thread start. Workspace identity is verified against the live binding. */
|
|
684
694
|
async sendMessageForAgent(agent, request) {
|
|
685
|
-
this.
|
|
686
|
-
const actor = this.memberActor(agent);
|
|
687
|
-
this.requireAgentWorkspace(actor, request.workspaceId);
|
|
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
|
-
});
|
|
694
|
-
this.emitCommittedOutcome(result);
|
|
695
|
-
return result.value;
|
|
695
|
+
return this.sendMessageAs(this.memberCall(agent, request.workspaceId), request);
|
|
696
696
|
}
|
|
697
697
|
/** Agent-only existing-Thread reply. */
|
|
698
698
|
async replyForAgent(agent, request) {
|
|
699
|
-
this.
|
|
700
|
-
const actor = this.memberActor(agent);
|
|
701
|
-
this.requireAgentWorkspace(actor, request.workspaceId);
|
|
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
|
-
});
|
|
708
|
-
this.emitCommittedOutcome(result);
|
|
709
|
-
return result.value;
|
|
699
|
+
return this.replyAs(this.memberCall(agent, request.workspaceId), request);
|
|
710
700
|
}
|
|
711
701
|
/** Agent-only personal Attention change. */
|
|
712
702
|
async changeAttentionForAgent(agent, request) {
|
|
713
|
-
this.
|
|
714
|
-
const actor = this.memberActor(agent);
|
|
715
|
-
this.requireAgentWorkspace(actor, request.workspaceId);
|
|
703
|
+
const actor = this.memberCall(agent, request.workspaceId);
|
|
716
704
|
const result = await this.requireLedger().changeAttention({ ...request, actor });
|
|
717
705
|
if (result.committed)
|
|
718
706
|
this.emitCommitted(result.value.receipt);
|
|
@@ -725,9 +713,7 @@ let AgentTeam = (() => {
|
|
|
725
713
|
}
|
|
726
714
|
/** Agent-only Claim mutation. */
|
|
727
715
|
async changeClaimForAgent(agent, request) {
|
|
728
|
-
this.
|
|
729
|
-
const actor = this.memberActor(agent);
|
|
730
|
-
this.requireAgentWorkspace(actor, request.workspaceId);
|
|
716
|
+
const actor = this.memberCall(agent, request.workspaceId);
|
|
731
717
|
const result = await this.requireLedger().changeClaim({ ...request, actor });
|
|
732
718
|
this.emitCommittedOutcome(result);
|
|
733
719
|
return result.value;
|
|
@@ -743,9 +729,7 @@ let AgentTeam = (() => {
|
|
|
743
729
|
return this.requireLedger().inbox(actor, request);
|
|
744
730
|
}
|
|
745
731
|
async readThreadForAgent(agent, request) {
|
|
746
|
-
this.
|
|
747
|
-
const actor = this.memberActor(agent);
|
|
748
|
-
this.requireAgentWorkspace(actor, request.workspaceId);
|
|
732
|
+
const actor = this.memberCall(agent, request.workspaceId);
|
|
749
733
|
const result = await this.requireLedger().readThread({ ...request, actor });
|
|
750
734
|
if (result.committed)
|
|
751
735
|
this.emitCommitted(result.value.receipt);
|
|
@@ -782,6 +766,41 @@ let AgentTeam = (() => {
|
|
|
782
766
|
throw new Error(`Agent Member '${memberId}' is not available for Channel membership`);
|
|
783
767
|
}
|
|
784
768
|
}
|
|
769
|
+
/** Shared Task-creation commit: resolve uploads into metadata lines and append through the ledger. */
|
|
770
|
+
async sendMessageAs(actor, request) {
|
|
771
|
+
const metadata = await this.resolveMessageAttachments(request);
|
|
772
|
+
const result = await this.requireLedger().sendMessage({
|
|
773
|
+
...request, body: this.appendAttachmentLines(request.body, metadata),
|
|
774
|
+
...(metadata.length === 0 ? {} : { resolvedAttachments: metadata }),
|
|
775
|
+
actor,
|
|
776
|
+
});
|
|
777
|
+
this.emitCommittedOutcome(result);
|
|
778
|
+
return result.value;
|
|
779
|
+
}
|
|
780
|
+
/** Shared existing-Thread reply commit: same upload resolution and outcome emission. */
|
|
781
|
+
async replyAs(actor, request) {
|
|
782
|
+
const metadata = await this.resolveMessageAttachments(request);
|
|
783
|
+
const result = await this.requireLedger().reply({
|
|
784
|
+
...request, body: this.appendAttachmentLines(request.body, metadata),
|
|
785
|
+
...(metadata.length === 0 ? {} : { resolvedAttachments: metadata }),
|
|
786
|
+
actor,
|
|
787
|
+
});
|
|
788
|
+
this.emitCommittedOutcome(result);
|
|
789
|
+
return result.value;
|
|
790
|
+
}
|
|
791
|
+
/** Fence one Human Remote call: accepting Host, known Workspace, Human actor. */
|
|
792
|
+
humanCall(workspaceId) {
|
|
793
|
+
this.requireAccepting();
|
|
794
|
+
this.requireWorkspace(workspaceId);
|
|
795
|
+
return agentTeamHumanActor();
|
|
796
|
+
}
|
|
797
|
+
/** Fence one Member call: accepting Host, live Member binding, matching Workspace. */
|
|
798
|
+
memberCall(agent, workspaceId) {
|
|
799
|
+
this.requireAccepting();
|
|
800
|
+
const actor = this.memberActor(agent);
|
|
801
|
+
this.requireAgentWorkspace(actor, workspaceId);
|
|
802
|
+
return actor;
|
|
803
|
+
}
|
|
785
804
|
memberActor(agent) {
|
|
786
805
|
const member = this.memberForAgent(agent);
|
|
787
806
|
if (member === undefined)
|
|
@@ -853,20 +872,50 @@ let AgentTeam = (() => {
|
|
|
853
872
|
await workspace.attachSession(member.sessionId);
|
|
854
873
|
this.handles.set(member.memberId, created);
|
|
855
874
|
this.modelSelections.set(member.memberId, selected);
|
|
856
|
-
this.
|
|
875
|
+
this.clearMemberFailure(member.memberId, 'activation');
|
|
857
876
|
this.nameMemberSession(member, created.agent);
|
|
858
877
|
this.notifyMember(created.agent);
|
|
878
|
+
this.autoCompaction.activated(member.memberId);
|
|
859
879
|
}
|
|
860
880
|
catch (error) {
|
|
861
881
|
await created?.dispose();
|
|
862
882
|
this.modelSelections.delete(member.memberId);
|
|
863
|
-
this.
|
|
883
|
+
this.setMemberFailure(member.memberId, 'activation', error instanceof Error ? error.message : String(error));
|
|
864
884
|
}
|
|
865
885
|
finally {
|
|
866
886
|
// Activation only changes this Workspace's presence projection.
|
|
867
887
|
this.emitChanged([{ kind: 'workspace', workspaceId: member.workspaceId }]);
|
|
868
888
|
}
|
|
869
889
|
}
|
|
890
|
+
/**
|
|
891
|
+
* Rebuild one enabled Member in place from its persisted Session.
|
|
892
|
+
*
|
|
893
|
+
* A bundle-row reload tears down the preset roster subtree, which prunes the
|
|
894
|
+
* standing mount while live agents keep their dead scope bindings: the
|
|
895
|
+
* Member keeps its session but loses its composed tools and services.
|
|
896
|
+
* Re-running the preset composition requires a fresh Agent, and disposal is
|
|
897
|
+
* the cost — the Web Client marks the recreated Session unavailable until it
|
|
898
|
+
* is reopened, the same trade the shipped suspend/resume cycle makes.
|
|
899
|
+
*/
|
|
900
|
+
reactivateMember(memberId) {
|
|
901
|
+
return this.enqueueLifecycle(async () => {
|
|
902
|
+
const member = this.requireLedger().getMember(memberId);
|
|
903
|
+
if (member === undefined || member.state !== 'enabled')
|
|
904
|
+
return false;
|
|
905
|
+
const stale = this.handles.get(memberId);
|
|
906
|
+
if (stale !== undefined) {
|
|
907
|
+
this.handles.delete(memberId);
|
|
908
|
+
this.modelSelections.delete(memberId);
|
|
909
|
+
// The composition-loss diagnostic this heal answers is stale once the
|
|
910
|
+
// rebuild starts; a later activation must not resurface it.
|
|
911
|
+
this.clearMemberFailure(memberId, 'compaction');
|
|
912
|
+
this.emitAutoCompactionChanged(memberId);
|
|
913
|
+
await stale.dispose();
|
|
914
|
+
}
|
|
915
|
+
await this.activateMember(member);
|
|
916
|
+
return this.handles.has(memberId);
|
|
917
|
+
});
|
|
918
|
+
}
|
|
870
919
|
/**
|
|
871
920
|
* Default an untitled Member Session to its handle so the ordinary Session
|
|
872
921
|
* list names it. An explicit rename or any earlier title always wins; the
|
|
@@ -921,17 +970,35 @@ let AgentTeam = (() => {
|
|
|
921
970
|
return Object.freeze({ member, availability: 'inactive', presence: 'unavailable' });
|
|
922
971
|
if (member.state === 'suspended')
|
|
923
972
|
return Object.freeze({ member, availability: 'suspended', presence: 'unavailable' });
|
|
924
|
-
const
|
|
925
|
-
if (
|
|
926
|
-
return Object.freeze({ member, availability: 'unavailable', presence: 'unavailable', diagnostic });
|
|
973
|
+
const failures = this.memberFailures.get(member.memberId);
|
|
974
|
+
if (failures?.activation !== undefined)
|
|
975
|
+
return Object.freeze({ member, availability: 'unavailable', presence: 'unavailable', diagnostic: failures.activation });
|
|
927
976
|
const handle = this.handles.get(member.memberId);
|
|
928
977
|
if (handle === undefined)
|
|
929
978
|
return Object.freeze({ member, availability: 'unavailable', presence: 'unavailable' });
|
|
930
|
-
|
|
979
|
+
if (this.ctx.agentPresets.composedPreset(handle.agent.ctx) === undefined) {
|
|
980
|
+
return Object.freeze({ member, availability: 'active', presence: 'error', diagnostic: ORPHANED_MEMBER_DIAGNOSTIC });
|
|
981
|
+
}
|
|
982
|
+
const runtimeError = failures?.runtime ?? failures?.compaction;
|
|
931
983
|
if (runtimeError !== undefined)
|
|
932
984
|
return Object.freeze({ member, availability: 'active', presence: 'error', diagnostic: runtimeError });
|
|
933
985
|
return Object.freeze({ member, availability: 'active', presence: handle.agent.status === 'running' ? 'working' : 'available' });
|
|
934
986
|
}
|
|
987
|
+
setMemberFailure(memberId, slot, message) {
|
|
988
|
+
const failures = this.memberFailures.get(memberId) ?? {};
|
|
989
|
+
failures[slot] = message;
|
|
990
|
+
this.memberFailures.set(memberId, failures);
|
|
991
|
+
}
|
|
992
|
+
clearMemberFailure(memberId, slot) {
|
|
993
|
+
const failures = this.memberFailures.get(memberId);
|
|
994
|
+
if (failures === undefined || failures[slot] === undefined)
|
|
995
|
+
return false;
|
|
996
|
+
if (Object.keys(failures).length === 1)
|
|
997
|
+
this.memberFailures.delete(memberId);
|
|
998
|
+
else
|
|
999
|
+
delete failures[slot];
|
|
1000
|
+
return true;
|
|
1001
|
+
}
|
|
935
1002
|
requireWorkspace(workspaceId) {
|
|
936
1003
|
const workspace = this.ctx.workspaceRegistry.get(workspaceId);
|
|
937
1004
|
if (workspace === undefined)
|
|
@@ -961,6 +1028,13 @@ let AgentTeam = (() => {
|
|
|
961
1028
|
if (handle !== undefined)
|
|
962
1029
|
this.notifyMember(handle.agent);
|
|
963
1030
|
}
|
|
1031
|
+
const compactionMembers = acceptedTaskCompactionMembers(operation);
|
|
1032
|
+
if (compactionMembers !== undefined)
|
|
1033
|
+
this.autoCompaction.schedule(compactionMembers);
|
|
1034
|
+
}
|
|
1035
|
+
emitAutoCompactionChanged(memberId) {
|
|
1036
|
+
const workspaceId = this.ledger?.getMember(memberId)?.workspaceId;
|
|
1037
|
+
this.emitChanged(workspaceId === undefined ? undefined : [{ kind: 'workspace', workspaceId }]);
|
|
964
1038
|
}
|
|
965
1039
|
/** Wake from durable unread state with bounded facts for direct and state-changing work. */
|
|
966
1040
|
notifyMember(agent) {
|
|
@@ -977,12 +1051,19 @@ let AgentTeam = (() => {
|
|
|
977
1051
|
]));
|
|
978
1052
|
if (this.notifiedInbox.get(member.memberId) === signature)
|
|
979
1053
|
return;
|
|
980
|
-
const
|
|
981
|
-
|
|
982
|
-
|
|
1054
|
+
const pending = [...agent.inbox.nextStep, ...agent.inbox.nextTurn];
|
|
1055
|
+
// steerResume already combines the recovery instruction and these durable
|
|
1056
|
+
// facts. Its synchronous running transition must not replace that notice.
|
|
1057
|
+
if (pending.some(message => this.isRecoveryNotice(message))) {
|
|
1058
|
+
this.notifiedInbox.set(member.memberId, signature);
|
|
1059
|
+
return;
|
|
1060
|
+
}
|
|
1061
|
+
const existingInboxHint = pending.find(message => this.isInboxNotice(message));
|
|
1062
|
+
if (existingInboxHint !== undefined)
|
|
1063
|
+
agent.inbox.remove(existingInboxHint.id);
|
|
983
1064
|
const hint = createUserMessage({
|
|
984
1065
|
content: [{ type: 'text', text: this.notificationText(notifications, member.memberId) }],
|
|
985
|
-
source: { kind: 'plugin', plugin:
|
|
1066
|
+
source: { kind: 'plugin', plugin: AGENT_TEAM_PLUGIN_ID, form: 'notice', summary: INBOX_NOTICE_SUMMARY },
|
|
986
1067
|
});
|
|
987
1068
|
this.notifiedInbox.set(member.memberId, signature);
|
|
988
1069
|
try {
|
|
@@ -993,6 +1074,16 @@ let AgentTeam = (() => {
|
|
|
993
1074
|
throw error;
|
|
994
1075
|
}
|
|
995
1076
|
}
|
|
1077
|
+
isInboxNotice(message) {
|
|
1078
|
+
const source = message.source;
|
|
1079
|
+
return source.kind === 'plugin' && source.plugin === AGENT_TEAM_PLUGIN_ID
|
|
1080
|
+
&& source.form === 'notice' && source.summary === INBOX_NOTICE_SUMMARY;
|
|
1081
|
+
}
|
|
1082
|
+
isRecoveryNotice(message) {
|
|
1083
|
+
const source = message.source;
|
|
1084
|
+
return source.kind === 'plugin' && source.plugin === AGENT_TEAM_PLUGIN_ID
|
|
1085
|
+
&& source.form === 'notice' && source.summary === RECOVERY_NOTICE_SUMMARY;
|
|
1086
|
+
}
|
|
996
1087
|
notificationText(notifications, readerId) {
|
|
997
1088
|
const maxCharacters = 32 * 1024;
|
|
998
1089
|
const sections = ['Team Inbox has unread work.'];
|
|
@@ -1018,7 +1109,8 @@ let AgentTeam = (() => {
|
|
|
1018
1109
|
const sender = fact.message.sender === AGENT_TEAM_HUMAN_MEMBER_ID
|
|
1019
1110
|
? 'human' : this.requireLedger().getMember(fact.message.sender)?.handle ?? fact.message.sender;
|
|
1020
1111
|
const detail = ['Direct Team mention', `From: ${sender}`, `Channel: ${item.channelRef}`,
|
|
1021
|
-
|
|
1112
|
+
...(item.task === undefined ? [] : [`Task: ${item.task.taskRef}`]),
|
|
1113
|
+
`Thread: ${item.thread.threadRef}`, `Message ref: ${fact.message.messageRef}`,
|
|
1022
1114
|
`Message: ${this.boundedNotificationBody(fact.message.body)}`].join('\n');
|
|
1023
1115
|
if (append(detail))
|
|
1024
1116
|
detailedFactCount += 1;
|
|
@@ -1030,12 +1122,14 @@ let AgentTeam = (() => {
|
|
|
1030
1122
|
}
|
|
1031
1123
|
}
|
|
1032
1124
|
const ordinaryCount = facts.filter(entry => entry.fact.kind === 'message' && !entry.direct).length;
|
|
1033
|
-
if (ordinaryCount > 0)
|
|
1034
|
-
|
|
1125
|
+
if (ordinaryCount > 0) {
|
|
1126
|
+
const route = item.task === undefined ? `Thread ${item.thread.threadRef}` : `Task ${item.task.taskRef}`;
|
|
1127
|
+
append(`${route}: ${ordinaryCount} unread update${ordinaryCount === 1 ? '' : 's'}.`);
|
|
1128
|
+
}
|
|
1035
1129
|
}
|
|
1036
1130
|
if (omitted)
|
|
1037
1131
|
sections.push('More unread work remains in team_inbox; the automatic context is bounded.');
|
|
1038
|
-
sections.push('Use team_thread read with the relevant
|
|
1132
|
+
sections.push('Use team_thread read with the relevant threadRef before acting or replying. Use team_inbox only when you need to triage the remaining Threads.');
|
|
1039
1133
|
return sections.join('\n\n');
|
|
1040
1134
|
}
|
|
1041
1135
|
boundedNotificationBody(body) {
|
|
@@ -1057,10 +1151,29 @@ let AgentTeam = (() => {
|
|
|
1057
1151
|
if (activity.kind === 'claims_released') {
|
|
1058
1152
|
return `Team Task update\n${actor}'s Claims ${activity.claimRefs.join(', ')} were released on Task ${activity.taskRef}.`;
|
|
1059
1153
|
}
|
|
1154
|
+
if (activity.kind === 'promote') {
|
|
1155
|
+
return `Team Task update\n${actor} created Task ${activity.taskRef} from Thread ${activity.threadRef}; it is open for claims.`;
|
|
1156
|
+
}
|
|
1060
1157
|
const released = 'releasedClaimRefs' in activity && activity.releasedClaimRefs !== undefined && activity.releasedClaimRefs.length > 0
|
|
1061
1158
|
? ` Released Claims: ${activity.releasedClaimRefs.join(', ')}.` : '';
|
|
1062
1159
|
return `Team Task update\n${actor} ${activity.kind} Task ${activity.taskRef}.${released}`;
|
|
1063
1160
|
}
|
|
1161
|
+
/** Dispose one live Member Session and drop its per-Member runtime state. */
|
|
1162
|
+
async disposeMemberSession(memberId, member) {
|
|
1163
|
+
this.clearMemberRecoveryState(member);
|
|
1164
|
+
const handle = this.handles.get(memberId);
|
|
1165
|
+
if (handle !== undefined) {
|
|
1166
|
+
await handle.dispose();
|
|
1167
|
+
this.handles.delete(memberId);
|
|
1168
|
+
}
|
|
1169
|
+
this.modelSelections.delete(memberId);
|
|
1170
|
+
this.clearMemberFailure(memberId, 'activation');
|
|
1171
|
+
}
|
|
1172
|
+
clearMemberRecoveryState(member) {
|
|
1173
|
+
this.recovery.stopTracking(member.memberId);
|
|
1174
|
+
this.clearMemberFailure(member.memberId, 'runtime');
|
|
1175
|
+
this.clearMemberNotificationState(member.memberId);
|
|
1176
|
+
}
|
|
1064
1177
|
clearMemberNotificationState(memberId) {
|
|
1065
1178
|
this.notifiedInbox.delete(memberId);
|
|
1066
1179
|
}
|