@wowyuarm/dsh-agent-team 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/README.zh.md +2 -0
- package/package.json +12 -3
- package/packages/agent-team/README.md +1 -1
- package/packages/agent-team/README.zh.md +1 -1
- package/packages/agent-team/lib/attachments.js +11 -12
- package/packages/agent-team/lib/auto-compaction.js +163 -0
- package/packages/agent-team/lib/index.js +214 -130
- package/packages/agent-team/lib/ledger.js +77 -70
- package/packages/agent-team/lib/recovery.js +30 -35
- package/packages/agent-team/lib/typert.host.js +33 -23
- package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
- package/packages/agent-team/lib/typert.remote-client.js +21 -21
- 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 +40 -12
- package/packages/agent-team/lib/types/index.d.ts.map +1 -1
- package/packages/agent-team/lib/types/ledger.d.ts +13 -0
- 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/preset/team-member/agent.cordis.yml +6 -1
- package/packages/client-agent-team/README.md +1 -1
- package/packages/client-agent-team/README.zh.md +1 -1
- package/packages/client-agent-team/lib/client.js +942 -534
- 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 +5 -166
- package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamChannelPage.js +6 -20
- 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.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamComposer.js +0 -1
- 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.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamMessage.js +83 -43
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.js +10 -23
- package/packages/client-agent-team/lib/types/client/index.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/index.js +71 -1
- package/packages/client-agent-team/lib/types/client/member-session-input.d.ts +22 -0
- package/packages/client-agent-team/lib/types/client/member-session-input.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/member-session-input.js +98 -0
- package/packages/client-agent-team/lib/types/client/requests.d.ts +16 -0
- package/packages/client-agent-team/lib/types/client/requests.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/requests.js +22 -0
- package/packages/client-agent-team/lib/types/client/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 +43 -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 +61 -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/lib/index.js +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;
|
|
@@ -166,19 +171,26 @@ let AgentTeam = (() => {
|
|
|
166
171
|
handles = new Map();
|
|
167
172
|
/** Live selection refs let model edits take effect without disposing the Session. */
|
|
168
173
|
modelSelections = new Map();
|
|
169
|
-
|
|
170
|
-
|
|
174
|
+
/**
|
|
175
|
+
* Why one Member shows error presence, per failure source. Reads prefer
|
|
176
|
+
* activation, then runtime, then compaction; slots clear independently, so
|
|
177
|
+
* a recovered runtime error re-reveals an outstanding compaction failure.
|
|
178
|
+
* Keyed by Member rather than Session so a restarted Session cannot leak
|
|
179
|
+
* stale keys.
|
|
180
|
+
*/
|
|
181
|
+
memberFailures = new Map();
|
|
182
|
+
autoCompaction;
|
|
171
183
|
/** Agent ids with a turn in flight; restarts must wait for the boundary. */
|
|
172
184
|
runningAgents = new Set();
|
|
173
185
|
notifiedInbox = new Map();
|
|
174
186
|
attachmentGcTimer;
|
|
175
187
|
recovery = new RecoveryCoordinator({
|
|
176
|
-
|
|
177
|
-
this.ctx.logger.info(`agent-team: automatic recovery
|
|
178
|
-
this.injectRecovery(memberId
|
|
188
|
+
wake: memberId => {
|
|
189
|
+
this.ctx.logger.info(`agent-team: automatic recovery wakeup for member '${this.memberLabel(memberId)}' after consecutive recoverable failures`);
|
|
190
|
+
this.injectRecovery(memberId);
|
|
179
191
|
},
|
|
180
|
-
onStandDown: (memberId,
|
|
181
|
-
this.ctx.logger.warn(`agent-team: member '${this.memberLabel(memberId)}'
|
|
192
|
+
onStandDown: (memberId, consecutiveFailures) => {
|
|
193
|
+
this.ctx.logger.warn(`agent-team: member '${this.memberLabel(memberId)}' reached ${consecutiveFailures}/${RECOVERY_MAX_CONSECUTIVE_ERRORS} consecutive recoverable failures; leaving it in error for the operator`);
|
|
182
194
|
},
|
|
183
195
|
});
|
|
184
196
|
lifecycleTail = Promise.resolve();
|
|
@@ -187,6 +199,21 @@ let AgentTeam = (() => {
|
|
|
187
199
|
changeWaiters = new Set();
|
|
188
200
|
constructor(ctx) {
|
|
189
201
|
super(ctx, 'agentTeam');
|
|
202
|
+
this.autoCompaction = new AutoCompactionCoordinator({
|
|
203
|
+
agentForMember: memberId => this.handles.get(memberId)?.agent,
|
|
204
|
+
compactionForAgent: agent => this.ctx.agentPresets.serviceFor(agent, 'compaction'),
|
|
205
|
+
reactivate: memberId => this.reactivateMember(memberId),
|
|
206
|
+
failed: (memberId, _sessionId, diagnostic) => {
|
|
207
|
+
this.setMemberFailure(memberId, 'compaction', diagnostic);
|
|
208
|
+
this.emitAutoCompactionChanged(memberId);
|
|
209
|
+
},
|
|
210
|
+
cleared: memberId => {
|
|
211
|
+
if (!this.clearMemberFailure(memberId, 'compaction'))
|
|
212
|
+
return;
|
|
213
|
+
this.emitAutoCompactionChanged(memberId);
|
|
214
|
+
},
|
|
215
|
+
log: message => { this.ctx.logger.warn(`agent-team: ${message}`); },
|
|
216
|
+
});
|
|
190
217
|
}
|
|
191
218
|
/** Open the durable ledger and restore every enabled Member independently. */
|
|
192
219
|
async [Service.init]() {
|
|
@@ -195,10 +222,10 @@ let AgentTeam = (() => {
|
|
|
195
222
|
if (member === undefined)
|
|
196
223
|
return;
|
|
197
224
|
const message = error instanceof Error ? error.message : String(error);
|
|
198
|
-
this.
|
|
225
|
+
this.setMemberFailure(member.memberId, 'runtime', message);
|
|
199
226
|
const kind = classifyRecoverableError(message);
|
|
200
227
|
if (kind !== undefined)
|
|
201
|
-
this.ctx.logger.warn(`agent-team: member '${member.handle}' hit a recoverable
|
|
228
|
+
this.ctx.logger.warn(`agent-team: member '${member.handle}' hit a recoverable ${kind} error; recording a consecutive error occurrence`);
|
|
202
229
|
this.recovery.onError(member.memberId, message);
|
|
203
230
|
this.emitChanged([{ kind: 'workspace', workspaceId: member.workspaceId }]);
|
|
204
231
|
});
|
|
@@ -209,14 +236,14 @@ let AgentTeam = (() => {
|
|
|
209
236
|
else
|
|
210
237
|
this.runningAgents.delete(agent.id);
|
|
211
238
|
if (status === 'running' && member !== undefined) {
|
|
212
|
-
const recovered = this.
|
|
239
|
+
const recovered = this.clearMemberFailure(member.memberId, 'runtime');
|
|
213
240
|
if (recovered)
|
|
214
241
|
this.notifiedInbox.delete(member.memberId);
|
|
215
242
|
this.emitChanged([{ kind: 'workspace', workspaceId: member.workspaceId }]);
|
|
216
243
|
this.notifyMember(agent);
|
|
217
244
|
}
|
|
218
245
|
// A turn that ends without an error closes any automatic recovery episode.
|
|
219
|
-
if (status === 'idle' && member !== undefined &&
|
|
246
|
+
if (status === 'idle' && member !== undefined && this.memberFailures.get(member.memberId)?.runtime === undefined) {
|
|
220
247
|
this.recovery.onCleanTurnEnd(member.memberId);
|
|
221
248
|
}
|
|
222
249
|
});
|
|
@@ -224,6 +251,7 @@ let AgentTeam = (() => {
|
|
|
224
251
|
this.ctx.effect(() => async () => {
|
|
225
252
|
this.accepting = false;
|
|
226
253
|
this.recovery.dispose();
|
|
254
|
+
await this.autoCompaction.dispose();
|
|
227
255
|
if (this.attachmentGcTimer !== undefined)
|
|
228
256
|
clearInterval(this.attachmentGcTimer);
|
|
229
257
|
this.attachmentGcTimer = undefined;
|
|
@@ -264,7 +292,6 @@ let AgentTeam = (() => {
|
|
|
264
292
|
members() {
|
|
265
293
|
return this.requireLedger().listMembers().map(member => this.memberStatus(member));
|
|
266
294
|
}
|
|
267
|
-
/** Return only this Workspace's current Member projection to the Client. */
|
|
268
295
|
/** Read-only navigation lookup for branded Task refs found in message bodies. */
|
|
269
296
|
resolveTaskRefs(request) {
|
|
270
297
|
this.requireWorkspace(request.workspaceId);
|
|
@@ -277,6 +304,7 @@ let AgentTeam = (() => {
|
|
|
277
304
|
});
|
|
278
305
|
return Object.freeze({ resolved: Object.freeze(this.requireLedger().resolveTaskRefs(request.workspaceId, taskRefs)) });
|
|
279
306
|
}
|
|
307
|
+
/** Return only this Workspace's current Member projection to the Client. */
|
|
280
308
|
membersForClient(request) {
|
|
281
309
|
this.requireWorkspace(request.workspaceId);
|
|
282
310
|
return this.members()
|
|
@@ -382,14 +410,7 @@ let AgentTeam = (() => {
|
|
|
382
410
|
const result = await this.requireLedger().suspendMember({ ...request, actor: agentTeamHumanActor() });
|
|
383
411
|
if (result.committed)
|
|
384
412
|
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);
|
|
413
|
+
await this.disposeMemberSession(request.memberId, result.value.member);
|
|
393
414
|
return Object.freeze({ receipt: result.value.receipt, status: this.memberStatus(result.value.member) });
|
|
394
415
|
});
|
|
395
416
|
}
|
|
@@ -414,11 +435,17 @@ let AgentTeam = (() => {
|
|
|
414
435
|
const member = this.requireLedger().getMember(request.memberId);
|
|
415
436
|
if (member === undefined || member.workspaceId !== request.workspaceId)
|
|
416
437
|
throw new Error(`unknown Member '${request.memberId}' in workspace '${request.workspaceId}'`);
|
|
417
|
-
const storedError = this.storedErrorFor(member.memberId);
|
|
418
438
|
this.recovery.stopTracking(request.memberId);
|
|
419
|
-
|
|
439
|
+
// An orphaned composition cannot be steered: its tools are gone, so a
|
|
440
|
+
// continuation prompt reaches an inert Member. Rebuild the Agent in place.
|
|
441
|
+
const handle = this.handles.get(request.memberId);
|
|
442
|
+
if (handle !== undefined && this.ctx.agentPresets.composedPreset(handle.agent.ctx) === undefined) {
|
|
443
|
+
this.ctx.logger.info(`agent-team: rebuilding member '${member.handle}' after its preset composition was orphaned by a reload`);
|
|
444
|
+
await this.reactivateMember(request.memberId);
|
|
445
|
+
return Object.freeze({ status: this.memberStatus(member) });
|
|
446
|
+
}
|
|
420
447
|
this.ctx.logger.info(`agent-team: operator asked member '${member.handle}' to resume`);
|
|
421
|
-
this.steerResume(member, this.
|
|
448
|
+
this.steerResume(member, this.manualResumeText());
|
|
422
449
|
return Object.freeze({ status: this.memberStatus(member) });
|
|
423
450
|
}
|
|
424
451
|
/** Ledger handle for log lines; falls back to the raw id when unknown. */
|
|
@@ -439,26 +466,11 @@ let AgentTeam = (() => {
|
|
|
439
466
|
this.attachmentGcTimer = setInterval(() => { void sweep(); }, 24 * 60 * 60 * 1000);
|
|
440
467
|
this.attachmentGcTimer.unref?.();
|
|
441
468
|
}
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
const handle = this.handles.get(memberId);
|
|
445
|
-
if (handle === undefined)
|
|
446
|
-
return undefined;
|
|
447
|
-
return this.runtimeErrors.get(handle.agent.id);
|
|
469
|
+
automaticResumeText() {
|
|
470
|
+
return 'Your previous turn ended early due to a temporary service error. Please continue the work you were doing before the error.';
|
|
448
471
|
}
|
|
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;
|
|
472
|
+
manualResumeText() {
|
|
473
|
+
return 'The operator asked you to resume after the previous turn ended early. Please continue the work you were doing before the error.';
|
|
462
474
|
}
|
|
463
475
|
/**
|
|
464
476
|
* Steer one continuation message into a Member's live session. Throws when
|
|
@@ -473,18 +485,22 @@ let AgentTeam = (() => {
|
|
|
473
485
|
const body = notifications.length === 0 ? text : `${text}\n\n${this.notificationText(notifications, member.memberId)}`;
|
|
474
486
|
const hint = createUserMessage({
|
|
475
487
|
content: [{ type: 'text', text: body }],
|
|
476
|
-
source: { kind: 'plugin', plugin:
|
|
488
|
+
source: { kind: 'plugin', plugin: AGENT_TEAM_PLUGIN_ID, form: 'notice', summary: RECOVERY_NOTICE_SUMMARY },
|
|
477
489
|
});
|
|
490
|
+
for (const pending of [...handle.agent.inbox.nextStep, ...handle.agent.inbox.nextTurn]) {
|
|
491
|
+
if (this.isInboxNotice(pending))
|
|
492
|
+
handle.agent.inbox.remove(pending.id);
|
|
493
|
+
}
|
|
478
494
|
handle.agent.steer(hint);
|
|
479
495
|
}
|
|
480
|
-
/** Automatic-recovery
|
|
481
|
-
injectRecovery(memberId
|
|
496
|
+
/** Automatic-recovery wakeup; throwing tells the coordinator the target is gone. */
|
|
497
|
+
injectRecovery(memberId) {
|
|
482
498
|
const handle = this.handles.get(memberId);
|
|
483
499
|
const agent = handle?.agent;
|
|
484
500
|
const member = agent !== undefined ? this.memberForAgent(agent) : undefined;
|
|
485
501
|
if (agent === undefined || member === undefined || member.state !== 'enabled')
|
|
486
502
|
throw new Error(`member '${memberId}' cannot be recovered automatically`);
|
|
487
|
-
this.steerResume(member, this.
|
|
503
|
+
this.steerResume(member, this.automaticResumeText());
|
|
488
504
|
}
|
|
489
505
|
/**
|
|
490
506
|
* Human edit of one Member's mutable facts. A live model selection is
|
|
@@ -516,14 +532,7 @@ let AgentTeam = (() => {
|
|
|
516
532
|
const result = await this.requireLedger().removeMember({ ...request, actor: agentTeamHumanActor() });
|
|
517
533
|
if (result.committed)
|
|
518
534
|
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);
|
|
535
|
+
await this.disposeMemberSession(request.memberId, result.value.member);
|
|
527
536
|
await this.cleanupRemovedMember(result.value.member);
|
|
528
537
|
return result.value;
|
|
529
538
|
});
|
|
@@ -538,37 +547,26 @@ let AgentTeam = (() => {
|
|
|
538
547
|
}
|
|
539
548
|
/** Human-only Channel membership grant; it never injects historical Thread bodies. */
|
|
540
549
|
async joinChannel(request) {
|
|
541
|
-
this.
|
|
542
|
-
this.requireWorkspace(request.workspaceId);
|
|
550
|
+
const actor = this.humanCall(request.workspaceId);
|
|
543
551
|
const ledger = this.requireLedger();
|
|
544
552
|
if (!ledger.hasCommitted(request.requestId))
|
|
545
553
|
this.assertChannelMembersAvailable([request.memberId]);
|
|
546
|
-
const result = await ledger.joinChannel({ ...request, actor
|
|
554
|
+
const result = await ledger.joinChannel({ ...request, actor });
|
|
547
555
|
if (result.committed)
|
|
548
556
|
this.emitCommitted(result.value.receipt);
|
|
549
557
|
return result.value;
|
|
550
558
|
}
|
|
551
559
|
/** Human-only Channel membership removal and Channel-scoped cleanup. */
|
|
552
560
|
async removeChannelMember(request) {
|
|
553
|
-
this.
|
|
554
|
-
this.
|
|
555
|
-
const result = await this.requireLedger().removeChannelMember({ ...request, actor: agentTeamHumanActor() });
|
|
561
|
+
const actor = this.humanCall(request.workspaceId);
|
|
562
|
+
const result = await this.requireLedger().removeChannelMember({ ...request, actor });
|
|
556
563
|
if (result.committed)
|
|
557
564
|
this.emitCommitted(result.value.receipt);
|
|
558
565
|
return result.value;
|
|
559
566
|
}
|
|
560
567
|
/** Human top-level Task creation; unfollowed Agent mentions use the two-send result. */
|
|
561
568
|
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;
|
|
569
|
+
return this.sendMessageAs(this.humanCall(request.workspaceId), request);
|
|
572
570
|
}
|
|
573
571
|
/**
|
|
574
572
|
* Resolve uploaded ids and agent-supplied absolute paths into one attachment
|
|
@@ -606,7 +604,7 @@ let AgentTeam = (() => {
|
|
|
606
604
|
const trimmed = body.trim();
|
|
607
605
|
if (metadata.length === 0)
|
|
608
606
|
return trimmed;
|
|
609
|
-
const lines = metadata.map(attachment => `[attachment] ${
|
|
607
|
+
const lines = metadata.map(attachment => `[attachment] ${attachmentPayloadPath(attachment.attachmentId, attachment.name)}`);
|
|
610
608
|
return `${trimmed}\n${lines.join('\n')}`;
|
|
611
609
|
}
|
|
612
610
|
/** Upload one composer attachment into the cache; bytes are immutable once written. */
|
|
@@ -631,22 +629,12 @@ let AgentTeam = (() => {
|
|
|
631
629
|
}
|
|
632
630
|
/** Human existing-Thread reply; unread and revision conflicts are business outcomes. */
|
|
633
631
|
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;
|
|
632
|
+
return this.replyAs(this.humanCall(request.workspaceId), request);
|
|
644
633
|
}
|
|
645
634
|
/** Human's personal Attention operation. */
|
|
646
635
|
async changeAttention(request) {
|
|
647
|
-
this.
|
|
648
|
-
this.
|
|
649
|
-
const result = await this.requireLedger().changeAttention({ ...request, actor: agentTeamHumanActor() });
|
|
636
|
+
const actor = this.humanCall(request.workspaceId);
|
|
637
|
+
const result = await this.requireLedger().changeAttention({ ...request, actor });
|
|
650
638
|
if (result.committed)
|
|
651
639
|
this.emitCommitted(result.value.receipt);
|
|
652
640
|
return result.value;
|
|
@@ -658,9 +646,8 @@ let AgentTeam = (() => {
|
|
|
658
646
|
}
|
|
659
647
|
/** Human's durable, atomically acknowledged Thread read. */
|
|
660
648
|
async readThread(request) {
|
|
661
|
-
this.
|
|
662
|
-
this.
|
|
663
|
-
const result = await this.requireLedger().readThread({ ...request, actor: agentTeamHumanActor() });
|
|
649
|
+
const actor = this.humanCall(request.workspaceId);
|
|
650
|
+
const result = await this.requireLedger().readThread({ ...request, actor });
|
|
664
651
|
if (result.committed)
|
|
665
652
|
this.emitCommitted(result.value.receipt);
|
|
666
653
|
return result.value;
|
|
@@ -682,37 +669,15 @@ let AgentTeam = (() => {
|
|
|
682
669
|
}
|
|
683
670
|
/** Agent-only top-level Task creation. Workspace identity is verified against the live binding. */
|
|
684
671
|
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;
|
|
672
|
+
return this.sendMessageAs(this.memberCall(agent, request.workspaceId), request);
|
|
696
673
|
}
|
|
697
674
|
/** Agent-only existing-Thread reply. */
|
|
698
675
|
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;
|
|
676
|
+
return this.replyAs(this.memberCall(agent, request.workspaceId), request);
|
|
710
677
|
}
|
|
711
678
|
/** Agent-only personal Attention change. */
|
|
712
679
|
async changeAttentionForAgent(agent, request) {
|
|
713
|
-
this.
|
|
714
|
-
const actor = this.memberActor(agent);
|
|
715
|
-
this.requireAgentWorkspace(actor, request.workspaceId);
|
|
680
|
+
const actor = this.memberCall(agent, request.workspaceId);
|
|
716
681
|
const result = await this.requireLedger().changeAttention({ ...request, actor });
|
|
717
682
|
if (result.committed)
|
|
718
683
|
this.emitCommitted(result.value.receipt);
|
|
@@ -725,9 +690,7 @@ let AgentTeam = (() => {
|
|
|
725
690
|
}
|
|
726
691
|
/** Agent-only Claim mutation. */
|
|
727
692
|
async changeClaimForAgent(agent, request) {
|
|
728
|
-
this.
|
|
729
|
-
const actor = this.memberActor(agent);
|
|
730
|
-
this.requireAgentWorkspace(actor, request.workspaceId);
|
|
693
|
+
const actor = this.memberCall(agent, request.workspaceId);
|
|
731
694
|
const result = await this.requireLedger().changeClaim({ ...request, actor });
|
|
732
695
|
this.emitCommittedOutcome(result);
|
|
733
696
|
return result.value;
|
|
@@ -743,9 +706,7 @@ let AgentTeam = (() => {
|
|
|
743
706
|
return this.requireLedger().inbox(actor, request);
|
|
744
707
|
}
|
|
745
708
|
async readThreadForAgent(agent, request) {
|
|
746
|
-
this.
|
|
747
|
-
const actor = this.memberActor(agent);
|
|
748
|
-
this.requireAgentWorkspace(actor, request.workspaceId);
|
|
709
|
+
const actor = this.memberCall(agent, request.workspaceId);
|
|
749
710
|
const result = await this.requireLedger().readThread({ ...request, actor });
|
|
750
711
|
if (result.committed)
|
|
751
712
|
this.emitCommitted(result.value.receipt);
|
|
@@ -782,6 +743,41 @@ let AgentTeam = (() => {
|
|
|
782
743
|
throw new Error(`Agent Member '${memberId}' is not available for Channel membership`);
|
|
783
744
|
}
|
|
784
745
|
}
|
|
746
|
+
/** Shared Task-creation commit: resolve uploads into metadata lines and append through the ledger. */
|
|
747
|
+
async sendMessageAs(actor, request) {
|
|
748
|
+
const metadata = await this.resolveMessageAttachments(request);
|
|
749
|
+
const result = await this.requireLedger().sendMessage({
|
|
750
|
+
...request, body: this.appendAttachmentLines(request.body, metadata),
|
|
751
|
+
...(metadata.length === 0 ? {} : { resolvedAttachments: metadata }),
|
|
752
|
+
actor,
|
|
753
|
+
});
|
|
754
|
+
this.emitCommittedOutcome(result);
|
|
755
|
+
return result.value;
|
|
756
|
+
}
|
|
757
|
+
/** Shared existing-Thread reply commit: same upload resolution and outcome emission. */
|
|
758
|
+
async replyAs(actor, request) {
|
|
759
|
+
const metadata = await this.resolveMessageAttachments(request);
|
|
760
|
+
const result = await this.requireLedger().reply({
|
|
761
|
+
...request, body: this.appendAttachmentLines(request.body, metadata),
|
|
762
|
+
...(metadata.length === 0 ? {} : { resolvedAttachments: metadata }),
|
|
763
|
+
actor,
|
|
764
|
+
});
|
|
765
|
+
this.emitCommittedOutcome(result);
|
|
766
|
+
return result.value;
|
|
767
|
+
}
|
|
768
|
+
/** Fence one Human Remote call: accepting Host, known Workspace, Human actor. */
|
|
769
|
+
humanCall(workspaceId) {
|
|
770
|
+
this.requireAccepting();
|
|
771
|
+
this.requireWorkspace(workspaceId);
|
|
772
|
+
return agentTeamHumanActor();
|
|
773
|
+
}
|
|
774
|
+
/** Fence one Member call: accepting Host, live Member binding, matching Workspace. */
|
|
775
|
+
memberCall(agent, workspaceId) {
|
|
776
|
+
this.requireAccepting();
|
|
777
|
+
const actor = this.memberActor(agent);
|
|
778
|
+
this.requireAgentWorkspace(actor, workspaceId);
|
|
779
|
+
return actor;
|
|
780
|
+
}
|
|
785
781
|
memberActor(agent) {
|
|
786
782
|
const member = this.memberForAgent(agent);
|
|
787
783
|
if (member === undefined)
|
|
@@ -853,20 +849,50 @@ let AgentTeam = (() => {
|
|
|
853
849
|
await workspace.attachSession(member.sessionId);
|
|
854
850
|
this.handles.set(member.memberId, created);
|
|
855
851
|
this.modelSelections.set(member.memberId, selected);
|
|
856
|
-
this.
|
|
852
|
+
this.clearMemberFailure(member.memberId, 'activation');
|
|
857
853
|
this.nameMemberSession(member, created.agent);
|
|
858
854
|
this.notifyMember(created.agent);
|
|
855
|
+
this.autoCompaction.activated(member.memberId);
|
|
859
856
|
}
|
|
860
857
|
catch (error) {
|
|
861
858
|
await created?.dispose();
|
|
862
859
|
this.modelSelections.delete(member.memberId);
|
|
863
|
-
this.
|
|
860
|
+
this.setMemberFailure(member.memberId, 'activation', error instanceof Error ? error.message : String(error));
|
|
864
861
|
}
|
|
865
862
|
finally {
|
|
866
863
|
// Activation only changes this Workspace's presence projection.
|
|
867
864
|
this.emitChanged([{ kind: 'workspace', workspaceId: member.workspaceId }]);
|
|
868
865
|
}
|
|
869
866
|
}
|
|
867
|
+
/**
|
|
868
|
+
* Rebuild one enabled Member in place from its persisted Session.
|
|
869
|
+
*
|
|
870
|
+
* A bundle-row reload tears down the preset roster subtree, which prunes the
|
|
871
|
+
* standing mount while live agents keep their dead scope bindings: the
|
|
872
|
+
* Member keeps its session but loses its composed tools and services.
|
|
873
|
+
* Re-running the preset composition requires a fresh Agent, and disposal is
|
|
874
|
+
* the cost — the Web Client marks the recreated Session unavailable until it
|
|
875
|
+
* is reopened, the same trade the shipped suspend/resume cycle makes.
|
|
876
|
+
*/
|
|
877
|
+
reactivateMember(memberId) {
|
|
878
|
+
return this.enqueueLifecycle(async () => {
|
|
879
|
+
const member = this.requireLedger().getMember(memberId);
|
|
880
|
+
if (member === undefined || member.state !== 'enabled')
|
|
881
|
+
return false;
|
|
882
|
+
const stale = this.handles.get(memberId);
|
|
883
|
+
if (stale !== undefined) {
|
|
884
|
+
this.handles.delete(memberId);
|
|
885
|
+
this.modelSelections.delete(memberId);
|
|
886
|
+
// The composition-loss diagnostic this heal answers is stale once the
|
|
887
|
+
// rebuild starts; a later activation must not resurface it.
|
|
888
|
+
this.clearMemberFailure(memberId, 'compaction');
|
|
889
|
+
this.emitAutoCompactionChanged(memberId);
|
|
890
|
+
await stale.dispose();
|
|
891
|
+
}
|
|
892
|
+
await this.activateMember(member);
|
|
893
|
+
return this.handles.has(memberId);
|
|
894
|
+
});
|
|
895
|
+
}
|
|
870
896
|
/**
|
|
871
897
|
* Default an untitled Member Session to its handle so the ordinary Session
|
|
872
898
|
* list names it. An explicit rename or any earlier title always wins; the
|
|
@@ -921,17 +947,35 @@ let AgentTeam = (() => {
|
|
|
921
947
|
return Object.freeze({ member, availability: 'inactive', presence: 'unavailable' });
|
|
922
948
|
if (member.state === 'suspended')
|
|
923
949
|
return Object.freeze({ member, availability: 'suspended', presence: 'unavailable' });
|
|
924
|
-
const
|
|
925
|
-
if (
|
|
926
|
-
return Object.freeze({ member, availability: 'unavailable', presence: 'unavailable', diagnostic });
|
|
950
|
+
const failures = this.memberFailures.get(member.memberId);
|
|
951
|
+
if (failures?.activation !== undefined)
|
|
952
|
+
return Object.freeze({ member, availability: 'unavailable', presence: 'unavailable', diagnostic: failures.activation });
|
|
927
953
|
const handle = this.handles.get(member.memberId);
|
|
928
954
|
if (handle === undefined)
|
|
929
955
|
return Object.freeze({ member, availability: 'unavailable', presence: 'unavailable' });
|
|
930
|
-
|
|
956
|
+
if (this.ctx.agentPresets.composedPreset(handle.agent.ctx) === undefined) {
|
|
957
|
+
return Object.freeze({ member, availability: 'active', presence: 'error', diagnostic: ORPHANED_MEMBER_DIAGNOSTIC });
|
|
958
|
+
}
|
|
959
|
+
const runtimeError = failures?.runtime ?? failures?.compaction;
|
|
931
960
|
if (runtimeError !== undefined)
|
|
932
961
|
return Object.freeze({ member, availability: 'active', presence: 'error', diagnostic: runtimeError });
|
|
933
962
|
return Object.freeze({ member, availability: 'active', presence: handle.agent.status === 'running' ? 'working' : 'available' });
|
|
934
963
|
}
|
|
964
|
+
setMemberFailure(memberId, slot, message) {
|
|
965
|
+
const failures = this.memberFailures.get(memberId) ?? {};
|
|
966
|
+
failures[slot] = message;
|
|
967
|
+
this.memberFailures.set(memberId, failures);
|
|
968
|
+
}
|
|
969
|
+
clearMemberFailure(memberId, slot) {
|
|
970
|
+
const failures = this.memberFailures.get(memberId);
|
|
971
|
+
if (failures === undefined || failures[slot] === undefined)
|
|
972
|
+
return false;
|
|
973
|
+
if (Object.keys(failures).length === 1)
|
|
974
|
+
this.memberFailures.delete(memberId);
|
|
975
|
+
else
|
|
976
|
+
delete failures[slot];
|
|
977
|
+
return true;
|
|
978
|
+
}
|
|
935
979
|
requireWorkspace(workspaceId) {
|
|
936
980
|
const workspace = this.ctx.workspaceRegistry.get(workspaceId);
|
|
937
981
|
if (workspace === undefined)
|
|
@@ -961,6 +1005,13 @@ let AgentTeam = (() => {
|
|
|
961
1005
|
if (handle !== undefined)
|
|
962
1006
|
this.notifyMember(handle.agent);
|
|
963
1007
|
}
|
|
1008
|
+
const compactionMembers = acceptedTaskCompactionMembers(operation);
|
|
1009
|
+
if (compactionMembers !== undefined)
|
|
1010
|
+
this.autoCompaction.schedule(compactionMembers);
|
|
1011
|
+
}
|
|
1012
|
+
emitAutoCompactionChanged(memberId) {
|
|
1013
|
+
const workspaceId = this.ledger?.getMember(memberId)?.workspaceId;
|
|
1014
|
+
this.emitChanged(workspaceId === undefined ? undefined : [{ kind: 'workspace', workspaceId }]);
|
|
964
1015
|
}
|
|
965
1016
|
/** Wake from durable unread state with bounded facts for direct and state-changing work. */
|
|
966
1017
|
notifyMember(agent) {
|
|
@@ -977,12 +1028,19 @@ let AgentTeam = (() => {
|
|
|
977
1028
|
]));
|
|
978
1029
|
if (this.notifiedInbox.get(member.memberId) === signature)
|
|
979
1030
|
return;
|
|
980
|
-
const
|
|
981
|
-
|
|
982
|
-
|
|
1031
|
+
const pending = [...agent.inbox.nextStep, ...agent.inbox.nextTurn];
|
|
1032
|
+
// steerResume already combines the recovery instruction and these durable
|
|
1033
|
+
// facts. Its synchronous running transition must not replace that notice.
|
|
1034
|
+
if (pending.some(message => this.isRecoveryNotice(message))) {
|
|
1035
|
+
this.notifiedInbox.set(member.memberId, signature);
|
|
1036
|
+
return;
|
|
1037
|
+
}
|
|
1038
|
+
const existingInboxHint = pending.find(message => this.isInboxNotice(message));
|
|
1039
|
+
if (existingInboxHint !== undefined)
|
|
1040
|
+
agent.inbox.remove(existingInboxHint.id);
|
|
983
1041
|
const hint = createUserMessage({
|
|
984
1042
|
content: [{ type: 'text', text: this.notificationText(notifications, member.memberId) }],
|
|
985
|
-
source: { kind: 'plugin', plugin:
|
|
1043
|
+
source: { kind: 'plugin', plugin: AGENT_TEAM_PLUGIN_ID, form: 'notice', summary: INBOX_NOTICE_SUMMARY },
|
|
986
1044
|
});
|
|
987
1045
|
this.notifiedInbox.set(member.memberId, signature);
|
|
988
1046
|
try {
|
|
@@ -993,6 +1051,16 @@ let AgentTeam = (() => {
|
|
|
993
1051
|
throw error;
|
|
994
1052
|
}
|
|
995
1053
|
}
|
|
1054
|
+
isInboxNotice(message) {
|
|
1055
|
+
const source = message.source;
|
|
1056
|
+
return source.kind === 'plugin' && source.plugin === AGENT_TEAM_PLUGIN_ID
|
|
1057
|
+
&& source.form === 'notice' && source.summary === INBOX_NOTICE_SUMMARY;
|
|
1058
|
+
}
|
|
1059
|
+
isRecoveryNotice(message) {
|
|
1060
|
+
const source = message.source;
|
|
1061
|
+
return source.kind === 'plugin' && source.plugin === AGENT_TEAM_PLUGIN_ID
|
|
1062
|
+
&& source.form === 'notice' && source.summary === RECOVERY_NOTICE_SUMMARY;
|
|
1063
|
+
}
|
|
996
1064
|
notificationText(notifications, readerId) {
|
|
997
1065
|
const maxCharacters = 32 * 1024;
|
|
998
1066
|
const sections = ['Team Inbox has unread work.'];
|
|
@@ -1061,6 +1129,22 @@ let AgentTeam = (() => {
|
|
|
1061
1129
|
? ` Released Claims: ${activity.releasedClaimRefs.join(', ')}.` : '';
|
|
1062
1130
|
return `Team Task update\n${actor} ${activity.kind} Task ${activity.taskRef}.${released}`;
|
|
1063
1131
|
}
|
|
1132
|
+
/** Dispose one live Member Session and drop its per-Member runtime state. */
|
|
1133
|
+
async disposeMemberSession(memberId, member) {
|
|
1134
|
+
this.clearMemberRecoveryState(member);
|
|
1135
|
+
const handle = this.handles.get(memberId);
|
|
1136
|
+
if (handle !== undefined) {
|
|
1137
|
+
await handle.dispose();
|
|
1138
|
+
this.handles.delete(memberId);
|
|
1139
|
+
}
|
|
1140
|
+
this.modelSelections.delete(memberId);
|
|
1141
|
+
this.clearMemberFailure(memberId, 'activation');
|
|
1142
|
+
}
|
|
1143
|
+
clearMemberRecoveryState(member) {
|
|
1144
|
+
this.recovery.stopTracking(member.memberId);
|
|
1145
|
+
this.clearMemberFailure(member.memberId, 'runtime');
|
|
1146
|
+
this.clearMemberNotificationState(member.memberId);
|
|
1147
|
+
}
|
|
1064
1148
|
clearMemberNotificationState(memberId) {
|
|
1065
1149
|
this.notifiedInbox.delete(memberId);
|
|
1066
1150
|
}
|