@wowyuarm/dsh-agent-team 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/README.md +8 -1
  2. package/README.zh.md +9 -1
  3. package/package.json +12 -3
  4. package/packages/agent-team/README.md +3 -3
  5. package/packages/agent-team/README.zh.md +3 -3
  6. package/packages/agent-team/lib/attachments.js +11 -12
  7. package/packages/agent-team/lib/auto-compaction.js +163 -0
  8. package/packages/agent-team/lib/index.js +252 -139
  9. package/packages/agent-team/lib/ledger.js +344 -170
  10. package/packages/agent-team/lib/recovery.js +30 -35
  11. package/packages/agent-team/lib/spec.js +27 -11
  12. package/packages/agent-team/lib/typert.host.js +197 -88
  13. package/packages/agent-team/lib/typert.remote-client.d.ts +3 -1
  14. package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
  15. package/packages/agent-team/lib/typert.remote-client.js +139 -59
  16. package/packages/agent-team/lib/types/attachments.d.ts +2 -2
  17. package/packages/agent-team/lib/types/attachments.d.ts.map +1 -1
  18. package/packages/agent-team/lib/types/auto-compaction.d.ts +38 -0
  19. package/packages/agent-team/lib/types/auto-compaction.d.ts.map +1 -0
  20. package/packages/agent-team/lib/types/index.d.ts +52 -19
  21. package/packages/agent-team/lib/types/index.d.ts.map +1 -1
  22. package/packages/agent-team/lib/types/ledger.d.ts +29 -2
  23. package/packages/agent-team/lib/types/ledger.d.ts.map +1 -1
  24. package/packages/agent-team/lib/types/recovery.d.ts +15 -16
  25. package/packages/agent-team/lib/types/recovery.d.ts.map +1 -1
  26. package/packages/agent-team/lib/types/spec.d.ts.map +1 -1
  27. package/packages/agent-team/lib/types/types.d.ts +69 -30
  28. package/packages/agent-team/lib/types/types.d.ts.map +1 -1
  29. package/packages/agent-team/preset/team-member/agent.cordis.yml +7 -2
  30. package/packages/client-agent-team/README.md +2 -1
  31. package/packages/client-agent-team/README.zh.md +2 -1
  32. package/packages/client-agent-team/lib/client.js +1577 -863
  33. package/packages/client-agent-team/lib/client.js.map +1 -1
  34. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts.map +1 -1
  35. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.js +35 -174
  36. package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts.map +1 -1
  37. package/packages/client-agent-team/lib/types/client/TeamChannelPage.js +20 -30
  38. package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.d.ts.map +1 -1
  39. package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.js +3 -2
  40. package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts +6 -1
  41. package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts.map +1 -1
  42. package/packages/client-agent-team/lib/types/client/TeamComposer.js +4 -5
  43. package/packages/client-agent-team/lib/types/client/TeamConversation.d.ts +1 -1
  44. package/packages/client-agent-team/lib/types/client/TeamConversation.d.ts.map +1 -1
  45. package/packages/client-agent-team/lib/types/client/TeamConversation.js +3 -3
  46. package/packages/client-agent-team/lib/types/client/TeamMemberEditor.d.ts +34 -0
  47. package/packages/client-agent-team/lib/types/client/TeamMemberEditor.d.ts.map +1 -0
  48. package/packages/client-agent-team/lib/types/client/TeamMemberEditor.js +169 -0
  49. package/packages/client-agent-team/lib/types/client/TeamMemberSessionComposer.d.ts +17 -0
  50. package/packages/client-agent-team/lib/types/client/TeamMemberSessionComposer.d.ts.map +1 -0
  51. package/packages/client-agent-team/lib/types/client/TeamMemberSessionComposer.js +120 -0
  52. package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts +4 -1
  53. package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts.map +1 -1
  54. package/packages/client-agent-team/lib/types/client/TeamMessage.js +94 -47
  55. package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts +2 -1
  56. package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts.map +1 -1
  57. package/packages/client-agent-team/lib/types/client/TeamThreadPage.js +81 -39
  58. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts.map +1 -1
  59. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.js +2 -1
  60. package/packages/client-agent-team/lib/types/client/index.d.ts.map +1 -1
  61. package/packages/client-agent-team/lib/types/client/index.js +72 -1
  62. package/packages/client-agent-team/lib/types/client/locales.d.ts +24 -0
  63. package/packages/client-agent-team/lib/types/client/locales.d.ts.map +1 -1
  64. package/packages/client-agent-team/lib/types/client/locales.js +24 -0
  65. package/packages/client-agent-team/lib/types/client/member-session-input.d.ts +22 -0
  66. package/packages/client-agent-team/lib/types/client/member-session-input.d.ts.map +1 -0
  67. package/packages/client-agent-team/lib/types/client/member-session-input.js +98 -0
  68. package/packages/client-agent-team/lib/types/client/navigation.d.ts +1 -1
  69. package/packages/client-agent-team/lib/types/client/navigation.d.ts.map +1 -1
  70. package/packages/client-agent-team/lib/types/client/navigation.js +12 -6
  71. package/packages/client-agent-team/lib/types/client/requests.d.ts +16 -0
  72. package/packages/client-agent-team/lib/types/client/requests.d.ts.map +1 -0
  73. package/packages/client-agent-team/lib/types/client/requests.js +22 -0
  74. package/packages/client-agent-team/lib/types/client/slots.d.ts +2 -1
  75. package/packages/client-agent-team/lib/types/client/slots.d.ts.map +1 -1
  76. package/packages/client-agent-team/lib/types/client/task-refs.d.ts +6 -1
  77. package/packages/client-agent-team/lib/types/client/task-refs.d.ts.map +1 -1
  78. package/packages/client-agent-team/lib/types/client/task-refs.js +19 -0
  79. package/packages/client-agent-team/lib/types/client/team-formatters.d.ts +52 -3
  80. package/packages/client-agent-team/lib/types/client/team-formatters.d.ts.map +1 -1
  81. package/packages/client-agent-team/lib/types/client/team-formatters.js +74 -4
  82. package/packages/client-agent-team/lib/types/client/team-membership.d.ts.map +1 -1
  83. package/packages/client-agent-team/lib/types/client/team-membership.js +2 -1
  84. package/packages/tool-agent-team/README.md +7 -7
  85. package/packages/tool-agent-team/README.zh.md +7 -7
  86. package/packages/tool-agent-team/lib/index.js +38 -28
  87. package/packages/tool-agent-team/lib/types/index.d.ts.map +1 -1
  88. package/packages/client-agent-team/lib/types/client/TeamInboxPanel.d.ts +0 -13
  89. package/packages/client-agent-team/lib/types/client/TeamInboxPanel.d.ts.map +0 -1
  90. package/packages/client-agent-team/lib/types/client/TeamInboxPanel.js +0 -90
@@ -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, RECOVERY_MAX_ATTEMPTS } from "./recovery.js";
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
- diagnostics = new Map();
170
- runtimeErrors = new Map();
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
- inject: (memberId, attempt, kind) => {
177
- this.ctx.logger.info(`agent-team: automatic recovery steering member '${this.memberLabel(memberId)}' (attempt ${attempt}/${RECOVERY_MAX_ATTEMPTS}, ${kind})`);
178
- this.injectRecovery(memberId, attempt, kind);
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, attempts) => {
181
- this.ctx.logger.warn(`agent-team: member '${this.memberLabel(memberId)}' still failing after ${attempts} automatic recovery attempts; leaving it in error for the operator`);
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.runtimeErrors.set(agent.id, message);
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 error (${kind}); automatic recovery scheduled`);
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.runtimeErrors.delete(agent.id);
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 && !this.runtimeErrors.has(agent.id)) {
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
- const handle = this.handles.get(request.memberId);
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 an error-stopped Member: steer a continuation prompt
409
- * into its live session. Runtime-only no ledger operation, no suspend.
410
- * Taking over manually also cancels any pending automatic recovery episode.
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
- const kind = classifyRecoverableError(storedError ?? '');
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.resumeText(kind ?? 'unspecified failure', { operatorRequested: true }));
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
- /** Latest runtime error summary for a Member's live session, if any. */
443
- storedErrorFor(memberId) {
444
- const handle = this.handles.get(memberId);
445
- if (handle === undefined)
446
- return undefined;
447
- return this.runtimeErrors.get(handle.agent.id);
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
- * Continuation prompt per the recovery design: name the error family,
451
- * annotate automatic attempts, and on the final attempt ask for a graceful
452
- * handoff instead of another crash.
453
- */
454
- resumeText(kindLabel, options) {
455
- const opening = options?.operatorRequested === true ? 'The operator asked you to resume. ' : '';
456
- const attemptNote = options?.attempt === undefined ? '' : ` (automatic recovery, attempt ${options.attempt}/${RECOVERY_MAX_ATTEMPTS})`;
457
- let text = `${opening}Your previous turn ended early due to an error (${kindLabel})${attemptNote}. Please continue your work from where it stopped.`;
458
- if (options?.attempt === RECOVERY_MAX_ATTEMPTS) {
459
- text += ' If the same error recurs, stop and leave a short handoff note for the operator.';
460
- }
461
- return text;
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: '@wowyuarm/dsh-agent-team', form: 'notice', summary: 'Recovery: continue your interrupted work.' },
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 injection; throwing tells the coordinator the target is gone. */
481
- injectRecovery(memberId, attempt, kind) {
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.resumeText(kind, { attempt }));
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
- const handle = this.handles.get(request.memberId);
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 Channel membership grant; it never injects historical Thread bodies. */
540
- async joinChannel(request) {
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: agentTeamHumanActor() });
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.requireAccepting();
554
- this.requireWorkspace(request.workspaceId);
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 Task creation; unfollowed Agent mentions use the two-send result. */
590
+ /** Human top-level Thread start; asTask attaches an optional Task overlay. */
561
591
  async sendMessage(request) {
562
- this.requireAccepting();
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] ${dshHomePath('agent-team', 'attachments', 'v1', attachment.attachmentId, attachment.name)}`);
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.requireAccepting();
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.requireAccepting();
648
- this.requireWorkspace(request.workspaceId);
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.requireAccepting();
662
- this.requireWorkspace(request.workspaceId);
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 Task creation. Workspace identity is verified against the live binding. */
693
+ /** Agent-only top-level Thread start. Workspace identity is verified against the live binding. */
684
694
  async sendMessageForAgent(agent, request) {
685
- this.requireAccepting();
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.requireAccepting();
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.requireAccepting();
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.requireAccepting();
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.requireAccepting();
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.diagnostics.delete(member.memberId);
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.diagnostics.set(member.memberId, error instanceof Error ? error.message : String(error));
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 diagnostic = this.diagnostics.get(member.memberId);
925
- if (diagnostic !== undefined)
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
- const runtimeError = this.runtimeErrors.get(handle.agent.id);
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 existingHint = [...agent.inbox.nextStep, ...agent.inbox.nextTurn].find(message => message.source.kind === 'plugin' && message.source.plugin === '@wowyuarm/dsh-agent-team');
981
- if (existingHint !== undefined)
982
- agent.inbox.remove(existingHint.id);
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: '@wowyuarm/dsh-agent-team', form: 'notice', summary: 'Team Inbox has unread work.' },
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
- `Task: ${item.task.taskRef}`, `Thread: ${item.thread.threadRef}`, `Message ref: ${fact.message.messageRef}`,
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
- append(`Task ${item.task.taskRef}: ${ordinaryCount} unread update${ordinaryCount === 1 ? '' : 's'}.`);
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 taskRef before acting or replying. Use team_inbox only when you need to triage the remaining Threads.');
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
  }