@wowyuarm/dsh-agent-team 0.1.4 → 0.1.6
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 +15 -1
- package/README.zh.md +20 -3
- package/package.json +7 -2
- package/packages/agent-team/README.md +6 -0
- package/packages/agent-team/README.zh.md +6 -0
- package/packages/agent-team/core-skills/member-skill-manager/SKILL.md +41 -0
- package/packages/agent-team/core-skills/member-skill-manager/references/auth-and-config.md +30 -0
- package/packages/agent-team/core-skills/member-skill-manager/references/writing-great-skills.md +17 -0
- package/packages/agent-team/lib/auto-compaction.js +39 -1
- package/packages/agent-team/lib/index.js +340 -7
- package/packages/agent-team/lib/ledger.js +219 -7
- package/packages/agent-team/lib/member-context.js +2 -2
- package/packages/agent-team/lib/member-skills.js +94 -0
- package/packages/agent-team/lib/spec.js +38 -0
- package/packages/agent-team/lib/typert.host.js +202 -27
- 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 +155 -22
- package/packages/agent-team/lib/types/auto-compaction.d.ts +9 -0
- package/packages/agent-team/lib/types/auto-compaction.d.ts.map +1 -1
- package/packages/agent-team/lib/types/index.d.ts +81 -1
- package/packages/agent-team/lib/types/index.d.ts.map +1 -1
- package/packages/agent-team/lib/types/ledger.d.ts +49 -1
- package/packages/agent-team/lib/types/ledger.d.ts.map +1 -1
- package/packages/agent-team/lib/types/member-skills.d.ts +45 -0
- package/packages/agent-team/lib/types/member-skills.d.ts.map +1 -0
- package/packages/agent-team/lib/types/spec.d.ts.map +1 -1
- package/packages/agent-team/lib/types/types.d.ts +127 -2
- package/packages/agent-team/lib/types/types.d.ts.map +1 -1
- package/packages/agent-team/preset/team-member/agent.cordis.yml +7 -4
- 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 +677 -363
- package/packages/client-agent-team/lib/client.js.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts +2 -4
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.js +48 -24
- 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 -0
- 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 -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 +73 -17
- package/packages/client-agent-team/lib/types/client/TeamMemberEditor.d.ts +4 -8
- package/packages/client-agent-team/lib/types/client/TeamMemberEditor.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamMemberEditor.js +7 -40
- package/packages/client-agent-team/lib/types/client/TeamMemberSessionComposer.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamMemberSessionComposer.js +16 -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 +48 -18
- package/packages/client-agent-team/lib/types/client/TeamRowMenu.d.ts +3 -2
- package/packages/client-agent-team/lib/types/client/TeamRowMenu.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamSidebarSection.d.ts +5 -2
- package/packages/client-agent-team/lib/types/client/TeamSidebarSection.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamSidebarSection.js +2 -4
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.js +82 -22
- package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts +1 -1
- package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.js +4 -2
- package/packages/client-agent-team/lib/types/client/index.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/index.js +15 -13
- package/packages/client-agent-team/lib/types/client/locales.d.ts +20 -16
- package/packages/client-agent-team/lib/types/client/locales.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/locales.js +20 -16
- package/packages/client-agent-team/lib/types/client/sidebar-sections.d.ts +16 -0
- package/packages/client-agent-team/lib/types/client/sidebar-sections.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/sidebar-sections.js +102 -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/team-formatters.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/team-formatters.js +9 -2
- package/packages/client-agent-team/lib/types/client/timeline-scroll.d.ts +5 -7
- package/packages/client-agent-team/lib/types/client/timeline-scroll.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/timeline-scroll.js +9 -22
- package/packages/tool-agent-team/lib/index.js +25 -5
- package/packages/tool-agent-team/lib/types/index.d.ts.map +1 -1
|
@@ -41,7 +41,8 @@ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn,
|
|
|
41
41
|
};
|
|
42
42
|
import { randomUUID } from 'node:crypto';
|
|
43
43
|
import { mkdir, rm, writeFile } from 'node:fs/promises';
|
|
44
|
-
import { join } from 'node:path';
|
|
44
|
+
import { dirname, join, resolve } from 'node:path';
|
|
45
|
+
import { fileURLToPath } from 'node:url';
|
|
45
46
|
import { isDeepStrictEqual } from 'node:util';
|
|
46
47
|
import { Service } from '@deepseek-ai/cordis';
|
|
47
48
|
import { installModelSelection } from '@deepseek-ai/dsh-agent';
|
|
@@ -52,8 +53,9 @@ import { SessionId } from '@deepseek-ai/dsh-session';
|
|
|
52
53
|
import { effectiveSandboxMode, setSandboxMode } from '@deepseek-ai/dsh-sandbox-policy';
|
|
53
54
|
import { Remote, TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
|
|
54
55
|
import { ATTACHMENT_MAX_BYTES, attachmentPayloadPath, attachmentsRoot, copyPathAttachment, newAttachmentId, readAttachment, sanitizeMediaType, sweepAttachmentCache, validatePathAttachment, writeAttachment } from "./attachments.js";
|
|
55
|
-
import { acceptedTaskCompactionMembers, AutoCompactionCoordinator } from "./auto-compaction.js";
|
|
56
|
+
import { acceptedTaskCompactionMembers, AutoCompactionCoordinator, PRE_COMPACTION_NOTICE_SUMMARY, preCompactionNoticeText } from "./auto-compaction.js";
|
|
56
57
|
import { AGENT_TEAM_HUMAN_MEMBER_ID, AgentTeamLedger, agentTeamHumanActor } from "./ledger.js";
|
|
58
|
+
import * as memberSkills from "./member-skills.js";
|
|
57
59
|
import { classifyRecoverableError, RecoveryCoordinator, RECOVERY_MAX_CONSECUTIVE_ERRORS } from "./recovery.js";
|
|
58
60
|
import { agentTeamDomainSpec } from "./spec.js";
|
|
59
61
|
export { agentTeamDomainSpec, agentTeamOperationSchema } from "./spec.js";
|
|
@@ -61,6 +63,13 @@ export { AGENT_TEAM_HUMAN_MEMBER_ID, AGENT_TEAM_INITIALIZE_REQUEST_ID } from "./
|
|
|
61
63
|
/** Process-stable marker carried by the final Team message tool definition. */
|
|
62
64
|
export const AGENT_TEAM_PRESET_MARKER = Symbol.for('@wowyuarm/dsh-agent-team.preset');
|
|
63
65
|
const AGENT_TEAM_PLUGIN_ID = '@wowyuarm/dsh-agent-team';
|
|
66
|
+
/**
|
|
67
|
+
* Read-only core skills shipped beside the preset (the meta skill first:
|
|
68
|
+
* its description routes any skill-management work to itself). Resolved
|
|
69
|
+
* from this module's emitted location so it follows the installed plugin,
|
|
70
|
+
* like the preset roster's own root.
|
|
71
|
+
*/
|
|
72
|
+
const BUNDLED_SKILLS_DIRECTORY = resolve(dirname(fileURLToPath(import.meta.url)), '../core-skills');
|
|
64
73
|
const INBOX_NOTICE_SUMMARY = 'Team Inbox has unread work.';
|
|
65
74
|
const RECOVERY_NOTICE_SUMMARY = 'Recovery: continue your interrupted work.';
|
|
66
75
|
const ORPHANED_MEMBER_DIAGNOSTIC = 'Member preset composition was lost after a reload; its tools are unavailable. Resume rebuilds the member in place.';
|
|
@@ -73,6 +82,14 @@ function sameChangeScope(left, right) {
|
|
|
73
82
|
return left.threadRef === right.threadRef;
|
|
74
83
|
return false;
|
|
75
84
|
}
|
|
85
|
+
/** Copy a Remote-supplied capability overlay into owned frozen storage. */
|
|
86
|
+
function deepCopyCapabilities(capabilities) {
|
|
87
|
+
const copyAllow = (allow) => allow === undefined ? {} : { allow: [...allow] };
|
|
88
|
+
return {
|
|
89
|
+
...(capabilities.tools === undefined ? {} : { tools: copyAllow(capabilities.tools.allow) }),
|
|
90
|
+
...(capabilities.skills === undefined ? {} : { skills: copyAllow(capabilities.skills.allow) }),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
76
93
|
/** Mark the preset's `team_message` definition as an Agent Team consumer. */
|
|
77
94
|
export function markAgentTeamPreset(definition) {
|
|
78
95
|
Object.defineProperty(definition, AGENT_TEAM_PRESET_MARKER, { value: true });
|
|
@@ -86,6 +103,22 @@ export const AGENT_TEAM_TOOL_NAMES = Object.freeze([
|
|
|
86
103
|
'team_claim',
|
|
87
104
|
'team_view',
|
|
88
105
|
]);
|
|
106
|
+
/**
|
|
107
|
+
* A DM was durably recorded but its session injection could not run (no live
|
|
108
|
+
* handle, or the wake itself failed). The recorded DM stays durable; the
|
|
109
|
+
* sender should not blindly retry — the recipient recovers it through its DM
|
|
110
|
+
* history once its session is live again.
|
|
111
|
+
*/
|
|
112
|
+
export class AgentTeamDmDeliveryError extends Error {
|
|
113
|
+
recipientMemberId;
|
|
114
|
+
recipientHandle;
|
|
115
|
+
constructor(recipientMemberId, recipientHandle, message) {
|
|
116
|
+
super(message);
|
|
117
|
+
this.recipientMemberId = recipientMemberId;
|
|
118
|
+
this.recipientHandle = recipientHandle;
|
|
119
|
+
this.name = 'AgentTeamDmDeliveryError';
|
|
120
|
+
}
|
|
121
|
+
}
|
|
89
122
|
let AgentTeam = (() => {
|
|
90
123
|
let _classSuper = TypertRemoteService;
|
|
91
124
|
let _instanceExtraInitializers = [];
|
|
@@ -96,6 +129,7 @@ let AgentTeam = (() => {
|
|
|
96
129
|
let _updateChannel_decorators;
|
|
97
130
|
let _addMember_decorators;
|
|
98
131
|
let _recoverMember_decorators;
|
|
132
|
+
let _clearMemberContext_decorators;
|
|
99
133
|
let _updateMember_decorators;
|
|
100
134
|
let _changeTask_decorators;
|
|
101
135
|
let _promoteThread_decorators;
|
|
@@ -121,6 +155,7 @@ let AgentTeam = (() => {
|
|
|
121
155
|
_updateChannel_decorators = [Remote('updateChannel')];
|
|
122
156
|
_addMember_decorators = [Remote('addMember')];
|
|
123
157
|
_recoverMember_decorators = [Remote('recoverMember')];
|
|
158
|
+
_clearMemberContext_decorators = [Remote('clearMemberContext')];
|
|
124
159
|
_updateMember_decorators = [Remote('updateMember')];
|
|
125
160
|
_changeTask_decorators = [Remote('changeTask')];
|
|
126
161
|
_promoteThread_decorators = [Remote('promoteThread')];
|
|
@@ -143,6 +178,7 @@ let AgentTeam = (() => {
|
|
|
143
178
|
__esDecorate(this, null, _updateChannel_decorators, { kind: "method", name: "updateChannel", static: false, private: false, access: { has: obj => "updateChannel" in obj, get: obj => obj.updateChannel }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
144
179
|
__esDecorate(this, null, _addMember_decorators, { kind: "method", name: "addMember", static: false, private: false, access: { has: obj => "addMember" in obj, get: obj => obj.addMember }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
145
180
|
__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);
|
|
181
|
+
__esDecorate(this, null, _clearMemberContext_decorators, { kind: "method", name: "clearMemberContext", static: false, private: false, access: { has: obj => "clearMemberContext" in obj, get: obj => obj.clearMemberContext }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
146
182
|
__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);
|
|
147
183
|
__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
184
|
__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);
|
|
@@ -174,6 +210,10 @@ let AgentTeam = (() => {
|
|
|
174
210
|
handles = new Map();
|
|
175
211
|
/** Live selection refs let model edits take effect without disposing the Session. */
|
|
176
212
|
modelSelections = new Map();
|
|
213
|
+
/** Live skill selection refs let capability edits re-filter the catalog without disposing the Session. */
|
|
214
|
+
skillSelections = new Map();
|
|
215
|
+
/** Per-Member private skill provider disposers; released with the Member's agent scope. */
|
|
216
|
+
skillProviderDisposals = new Map();
|
|
177
217
|
/**
|
|
178
218
|
* Why one Member shows error presence, per failure source. Reads prefer
|
|
179
219
|
* activation, then runtime, then compaction; slots clear independently, so
|
|
@@ -182,6 +222,20 @@ let AgentTeam = (() => {
|
|
|
182
222
|
* stale keys.
|
|
183
223
|
*/
|
|
184
224
|
memberFailures = new Map();
|
|
225
|
+
/**
|
|
226
|
+
* Live per-Member tool restriction disposers, mirroring modelSelections:
|
|
227
|
+
* activation registers, disposal paths release, edits swap at a turn
|
|
228
|
+
* boundary. Deliberate interface reservation: the restriction seam is the
|
|
229
|
+
* primitive future Runtime Revision manifests orchestrate — do not remove
|
|
230
|
+
* during cleanup.
|
|
231
|
+
*/
|
|
232
|
+
memberRestrictions = new Map();
|
|
233
|
+
/**
|
|
234
|
+
* Runtime-derived capability warnings, recomputed at every activation (like
|
|
235
|
+
* memberFailures, keyed by Member and never persisted): persisted warnings
|
|
236
|
+
* would lie after a Host restart or a Harness upgrade renames tools.
|
|
237
|
+
*/
|
|
238
|
+
capabilityWarnings = new Map();
|
|
185
239
|
autoCompaction;
|
|
186
240
|
/** Agent ids with a turn in flight; restarts must wait for the boundary. */
|
|
187
241
|
runningAgents = new Set();
|
|
@@ -216,6 +270,14 @@ let AgentTeam = (() => {
|
|
|
216
270
|
this.emitAutoCompactionChanged(memberId);
|
|
217
271
|
},
|
|
218
272
|
log: message => { this.ctx.logger.warn(`agent-team: ${message}`); },
|
|
273
|
+
// Advisory only: the Agent decides whether anything is worth persisting.
|
|
274
|
+
steerPreCompaction: agent => {
|
|
275
|
+
const hint = createUserMessage({
|
|
276
|
+
content: [{ type: 'text', text: preCompactionNoticeText() }],
|
|
277
|
+
source: { kind: 'plugin', plugin: AGENT_TEAM_PLUGIN_ID, form: 'notice', summary: PRE_COMPACTION_NOTICE_SUMMARY },
|
|
278
|
+
});
|
|
279
|
+
agent.steer(hint);
|
|
280
|
+
},
|
|
219
281
|
});
|
|
220
282
|
}
|
|
221
283
|
/** Open the durable ledger and restore every enabled Member independently. */
|
|
@@ -246,8 +308,15 @@ let AgentTeam = (() => {
|
|
|
246
308
|
this.notifyMember(agent);
|
|
247
309
|
}
|
|
248
310
|
// A turn that ends without an error closes any automatic recovery episode.
|
|
249
|
-
|
|
250
|
-
|
|
311
|
+
// The idle transition is itself presence-affecting (working → available),
|
|
312
|
+
// so it must wake workspace watchers exactly like the running transition
|
|
313
|
+
// above; without this wake, cached Client member rows keep showing the
|
|
314
|
+
// Member as working after every turn until an unrelated change arrives.
|
|
315
|
+
if (status === 'idle' && member !== undefined) {
|
|
316
|
+
if (this.memberFailures.get(member.memberId)?.runtime === undefined) {
|
|
317
|
+
this.recovery.onCleanTurnEnd(member.memberId);
|
|
318
|
+
}
|
|
319
|
+
this.emitChanged([{ kind: 'workspace', workspaceId: member.workspaceId }]);
|
|
251
320
|
}
|
|
252
321
|
});
|
|
253
322
|
const domain = await this.ctx.storageDomain.open(agentTeamDomainSpec);
|
|
@@ -263,6 +332,14 @@ let AgentTeam = (() => {
|
|
|
263
332
|
await Promise.all([...this.handles.values()].map(handle => handle.dispose()));
|
|
264
333
|
this.handles.clear();
|
|
265
334
|
this.modelSelections.clear();
|
|
335
|
+
this.skillSelections.clear();
|
|
336
|
+
for (const dispose of this.skillProviderDisposals.values())
|
|
337
|
+
dispose();
|
|
338
|
+
this.skillProviderDisposals.clear();
|
|
339
|
+
for (const dispose of this.memberRestrictions.values())
|
|
340
|
+
dispose();
|
|
341
|
+
this.memberRestrictions.clear();
|
|
342
|
+
this.capabilityWarnings.clear();
|
|
266
343
|
this.runningAgents.clear();
|
|
267
344
|
await domain.close();
|
|
268
345
|
}, 'agentTeam.dispose');
|
|
@@ -395,6 +472,7 @@ let AgentTeam = (() => {
|
|
|
395
472
|
description: request.description,
|
|
396
473
|
presetId: request.presetId,
|
|
397
474
|
...(request.model === undefined ? {} : { model: Object.freeze({ ...request.model }) }),
|
|
475
|
+
...(request.capabilities === undefined ? {} : { capabilities: Object.freeze(deepCopyCapabilities(request.capabilities)) }),
|
|
398
476
|
privateMemoryPath: dshHomePath('agent-team', 'members', memberId),
|
|
399
477
|
state: 'enabled',
|
|
400
478
|
});
|
|
@@ -463,6 +541,69 @@ let AgentTeam = (() => {
|
|
|
463
541
|
this.steerResume(member, this.manualResumeText());
|
|
464
542
|
return Object.freeze({ status: this.memberStatus(member) });
|
|
465
543
|
}
|
|
544
|
+
/**
|
|
545
|
+
* Start one enabled Member from a new context: dispose the live handle,
|
|
546
|
+
* archive the previous Session (its log stays on disk for history), and
|
|
547
|
+
* activate a fresh Session under a new sessionId, so preset, tools, private
|
|
548
|
+
* memory, and model selection all reload while the next turn carries no
|
|
549
|
+
* history. The durable operation moves the Member's sessionId; identity,
|
|
550
|
+
* memory path, and binding survive. A new id is what keeps the Web Client
|
|
551
|
+
* seat live: a disposed generation's resident instance keeps its `removed`
|
|
552
|
+
* bit forever, so renewing under the same id would leave a permanently
|
|
553
|
+
* grayed session view.
|
|
554
|
+
*/
|
|
555
|
+
async clearMemberContext(request) {
|
|
556
|
+
return this.enqueueLifecycle(async () => {
|
|
557
|
+
this.requireAccepting();
|
|
558
|
+
this.requireWorkspace(request.workspaceId);
|
|
559
|
+
const stored = this.requireLedger().getMember(request.memberId);
|
|
560
|
+
if (stored === undefined || stored.workspaceId !== request.workspaceId)
|
|
561
|
+
throw new Error(`unknown Member '${request.memberId}' in workspace '${request.workspaceId}'`);
|
|
562
|
+
if (stored.state !== 'enabled')
|
|
563
|
+
throw new Error(`Agent Member '${stored.handle}' is ${stored.state}; only enabled Members can start from a new context`);
|
|
564
|
+
const active = this.handles.get(request.memberId);
|
|
565
|
+
if (active === undefined)
|
|
566
|
+
throw new Error(`Agent Member '${stored.handle}' has no active session to clear`);
|
|
567
|
+
if (this.runningAgents.has(active.agent.id))
|
|
568
|
+
throw new Error(`Agent Member '${stored.handle}' is still running; wait for the current turn to end before starting from a new context`);
|
|
569
|
+
const previousSessionId = stored.sessionId;
|
|
570
|
+
// The fresh id derives from the requestId, so a retried identical
|
|
571
|
+
// request mints the same id and the ledger dedupes it instead of
|
|
572
|
+
// colliding; the format matches addMember's `agent-team-<uuid>`.
|
|
573
|
+
const sessionId = SessionId(`agent-team-${request.requestId}`);
|
|
574
|
+
const result = await this.requireLedger().renewMemberSession({ ...request, sessionId, actor: agentTeamHumanActor() });
|
|
575
|
+
if (result.committed)
|
|
576
|
+
this.emitCommitted(result.value.receipt);
|
|
577
|
+
else {
|
|
578
|
+
// A retried identical request already renewed this Member; report the
|
|
579
|
+
// recorded outcome without another dispose/reactivate cycle.
|
|
580
|
+
return Object.freeze({ receipt: result.value.receipt, status: this.memberStatus(result.value.member) });
|
|
581
|
+
}
|
|
582
|
+
const renewed = result.value.member;
|
|
583
|
+
// Drop the old handle's transient state: pending recovery episodes and
|
|
584
|
+
// error markers belong to the disposed agent, not to the Member.
|
|
585
|
+
this.recovery.stopTracking(request.memberId);
|
|
586
|
+
await active.dispose();
|
|
587
|
+
this.handles.delete(request.memberId);
|
|
588
|
+
this.modelSelections.delete(request.memberId);
|
|
589
|
+
this.skillSelections.delete(request.memberId);
|
|
590
|
+
this.releaseMemberToolPolicy(request.memberId);
|
|
591
|
+
this.releaseMemberSkillProvider(request.memberId);
|
|
592
|
+
this.clearMemberFailure(request.memberId, 'activation');
|
|
593
|
+
this.clearMemberNotificationState(request.memberId);
|
|
594
|
+
// The previous log survives on disk; archiving hides it from every
|
|
595
|
+
// grouping surface so one Member keeps exactly one visible Session.
|
|
596
|
+
await this.ctx.workspaceRegistry.archiveSession(previousSessionId);
|
|
597
|
+
await this.activateMember(renewed, undefined, undefined, previousSessionId);
|
|
598
|
+
const reactivated = this.handles.get(request.memberId);
|
|
599
|
+
if (reactivated === undefined) {
|
|
600
|
+
// Reactivation failed; the activation diagnostic carries the reason and
|
|
601
|
+
// the durable renewal stays honest about the attempt.
|
|
602
|
+
throw new Error(`Agent Member '${stored.handle}' failed to start a new context: ${this.memberFailures.get(request.memberId)?.activation ?? 'unknown error'}`);
|
|
603
|
+
}
|
|
604
|
+
return Object.freeze({ receipt: result.value.receipt, status: this.memberStatus(renewed) });
|
|
605
|
+
});
|
|
606
|
+
}
|
|
466
607
|
/** Ledger handle for log lines; falls back to the raw id when unknown. */
|
|
467
608
|
memberLabel(memberId) {
|
|
468
609
|
return this.ledger?.getMember(memberId)?.handle ?? memberId;
|
|
@@ -538,9 +679,55 @@ let AgentTeam = (() => {
|
|
|
538
679
|
throw new Error(`Agent Member '${stored.handle}' has no live model selection`);
|
|
539
680
|
selection.current = stored.model ?? this.ctx.agentDefaultModel.currentSelection();
|
|
540
681
|
}
|
|
682
|
+
if (active !== undefined && !isDeepStrictEqual(previous?.capabilities ?? undefined, stored.capabilities ?? undefined)) {
|
|
683
|
+
await this.applyCapabilityEdit(active, stored);
|
|
684
|
+
}
|
|
541
685
|
return Object.freeze({ receipt: result.value.receipt, status: this.memberStatus(stored) });
|
|
542
686
|
});
|
|
543
687
|
}
|
|
688
|
+
/**
|
|
689
|
+
* Live-apply a capability edit at a turn boundary: while the Agent runs, the
|
|
690
|
+
* current turn keeps its schemas and catalog; the swap happens once idle,
|
|
691
|
+
* so the next step recomputes schemas from the new restriction and the
|
|
692
|
+
* durable replacement skill catalog from the new selection, with the same
|
|
693
|
+
* Session and history surviving. Suspend/remove during the wait cancels
|
|
694
|
+
* the swap — the disposed handle released the old restriction already and
|
|
695
|
+
* no disposer leaks.
|
|
696
|
+
*/
|
|
697
|
+
async applyCapabilityEdit(active, stored) {
|
|
698
|
+
const memberId = stored.memberId;
|
|
699
|
+
if (this.runningAgents.has(active.agent.id)) {
|
|
700
|
+
await new Promise(resolve => {
|
|
701
|
+
const disposers = [];
|
|
702
|
+
const settle = () => {
|
|
703
|
+
for (const dispose of disposers.splice(0))
|
|
704
|
+
dispose();
|
|
705
|
+
resolve();
|
|
706
|
+
};
|
|
707
|
+
disposers.push(this.ctx.on('agent/status', (payload) => {
|
|
708
|
+
if (payload.agent !== active.agent)
|
|
709
|
+
return;
|
|
710
|
+
// A turn that ends — clean idle or error — is the boundary; the
|
|
711
|
+
// next step recomputes schemas from the new restriction.
|
|
712
|
+
if (payload.status !== 'running')
|
|
713
|
+
settle();
|
|
714
|
+
}),
|
|
715
|
+
// Disposal (suspend/remove/reactivation) resolves the wait: the old
|
|
716
|
+
// restriction went with the disposed scope, so only the ledger
|
|
717
|
+
// intent remains to apply at the next activation.
|
|
718
|
+
this.ctx.on('session/disposed', (session) => {
|
|
719
|
+
if (session.id === active.agent.session.id)
|
|
720
|
+
settle();
|
|
721
|
+
}));
|
|
722
|
+
});
|
|
723
|
+
if (this.handles.get(memberId) !== active)
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
726
|
+
this.reapplyMemberToolPolicy(stored);
|
|
727
|
+
const skillSelection = this.skillSelections.get(memberId);
|
|
728
|
+
if (skillSelection !== undefined)
|
|
729
|
+
skillSelection.swap(stored.capabilities?.skills?.allow);
|
|
730
|
+
}
|
|
544
731
|
/** Irreversibly remove one Member, archive its Session, and delete its private namespace. */
|
|
545
732
|
async removeMember(request) {
|
|
546
733
|
return this.enqueueLifecycle(async () => {
|
|
@@ -735,6 +922,49 @@ let AgentTeam = (() => {
|
|
|
735
922
|
this.emitCommitted(result.value.receipt);
|
|
736
923
|
return result.value;
|
|
737
924
|
}
|
|
925
|
+
/**
|
|
926
|
+
* Agent-only direct message: append the audit-only dm-sent operation, then
|
|
927
|
+
* inject the body into the recipient's live session. The ledger commit is
|
|
928
|
+
* the durable fact; the injection is a transient runtime effect, so a
|
|
929
|
+
* missing handle or a failed wake returns a structured delivery error while
|
|
930
|
+
* the recorded DM stays durable for the recipient's recovery path.
|
|
931
|
+
*/
|
|
932
|
+
async dmForAgent(agent, request) {
|
|
933
|
+
const actor = this.memberCall(agent, request.workspaceId);
|
|
934
|
+
const result = await this.requireLedger().sendDm({ ...request, actor });
|
|
935
|
+
if (!result.committed)
|
|
936
|
+
return result.value;
|
|
937
|
+
this.emitCommitted(result.value.receipt);
|
|
938
|
+
const recipient = result.value.recipient;
|
|
939
|
+
const handle = this.handles.get(recipient.memberId);
|
|
940
|
+
if (handle === undefined) {
|
|
941
|
+
throw new AgentTeamDmDeliveryError(recipient.memberId, recipient.handle, `DM recorded but not delivered: Agent Member '${recipient.handle}' has no live session; it will find the message in its DM history after recovery`);
|
|
942
|
+
}
|
|
943
|
+
try {
|
|
944
|
+
const message = createUserMessage({
|
|
945
|
+
content: [{ type: 'text', text: this.dmRelayText(agent, recipient, request.body.trim(), result.value.receipt.operationId) }],
|
|
946
|
+
source: { kind: 'plugin', plugin: AGENT_TEAM_PLUGIN_ID, form: 'relay' },
|
|
947
|
+
});
|
|
948
|
+
// An idle recipient gets one ordinary turn; a busy one is steered into
|
|
949
|
+
// its current turn — the same wake split subagent continuations use.
|
|
950
|
+
if (handle.agent.status === 'idle')
|
|
951
|
+
handle.agent.followup(message);
|
|
952
|
+
else
|
|
953
|
+
handle.agent.steer(message);
|
|
954
|
+
}
|
|
955
|
+
catch (error) {
|
|
956
|
+
throw new AgentTeamDmDeliveryError(recipient.memberId, recipient.handle, `DM recorded but not delivered: ${error instanceof Error ? error.message : String(error)}`);
|
|
957
|
+
}
|
|
958
|
+
return result.value;
|
|
959
|
+
}
|
|
960
|
+
/** Relay body: the DM itself plus one bounded line of adjacent context. */
|
|
961
|
+
dmRelayText(senderAgent, recipient, body, excluding) {
|
|
962
|
+
const sender = this.memberForAgent(senderAgent);
|
|
963
|
+
const prior = this.requireLedger().dmHistoryBetween(senderAgent.id, recipient.memberId, excluding);
|
|
964
|
+
const header = `Direct message from @${sender?.handle ?? 'a Team Member'}:`;
|
|
965
|
+
const context = prior === undefined ? '' : `\n\n[most recent prior DM between you: ${prior}]`;
|
|
966
|
+
return `${header}\n\n${body}${context}`;
|
|
967
|
+
}
|
|
738
968
|
threadHistoryForAgent(agent, request) {
|
|
739
969
|
const actor = this.memberActor(agent);
|
|
740
970
|
this.requireAgentWorkspace(actor, request.workspaceId);
|
|
@@ -831,7 +1061,7 @@ let AgentTeam = (() => {
|
|
|
831
1061
|
throw error;
|
|
832
1062
|
}
|
|
833
1063
|
}
|
|
834
|
-
async activateMember(member, knownWorkspacePath, knownSessions) {
|
|
1064
|
+
async activateMember(member, knownWorkspacePath, knownSessions, forkedFrom) {
|
|
835
1065
|
if (this.handles.has(member.memberId))
|
|
836
1066
|
return;
|
|
837
1067
|
let created;
|
|
@@ -847,8 +1077,13 @@ let AgentTeam = (() => {
|
|
|
847
1077
|
const selection = member.model ?? this.ctx.agentDefaultModel.currentSelection();
|
|
848
1078
|
const agentOptions = { provider: selection.provider, model: selection.model };
|
|
849
1079
|
const selected = { current: selection, assembled: undefined };
|
|
1080
|
+
// Absent skills.allow loads every discovered private skill; a present
|
|
1081
|
+
// allow-list filters the catalog by name through the live ref below.
|
|
1082
|
+
// `swap` is bound by the provider at activation (no-op until then).
|
|
1083
|
+
const skillSelection = { current: member.capabilities?.skills?.allow, swap: () => { } };
|
|
850
1084
|
const setup = async (agentCtx) => {
|
|
851
1085
|
await this.ctx.agentPresets.mount(agentCtx, member.presetId);
|
|
1086
|
+
this.applyMemberToolPolicy(agentCtx, member);
|
|
852
1087
|
this.validateMemberPreset(agentCtx);
|
|
853
1088
|
installModelSelection(agentCtx, selected);
|
|
854
1089
|
return {
|
|
@@ -865,13 +1100,25 @@ let AgentTeam = (() => {
|
|
|
865
1100
|
? await this.ctx.agents.resume({ resumeSessionId: member.sessionId, agentOptions, setup })
|
|
866
1101
|
: await this.ctx.agents.create({
|
|
867
1102
|
sessionId: member.sessionId,
|
|
868
|
-
|
|
1103
|
+
// A context renewal records its fork lineage so the archived
|
|
1104
|
+
// previous Session stays discoverable from the durable header.
|
|
1105
|
+
meta: { cwd: workspacePath, agentPreset: member.presetId, ...(forkedFrom === undefined ? {} : { parentSession: forkedFrom }) },
|
|
869
1106
|
agentOptions,
|
|
870
1107
|
setup,
|
|
871
1108
|
});
|
|
1109
|
+
// The Member-private skill provider registers on the created agent's
|
|
1110
|
+
// exact scope layer (the traceable-service seam, like the tool
|
|
1111
|
+
// restriction): bundled read-only core skills plus this Member's own
|
|
1112
|
+
// private directory.
|
|
1113
|
+
this.skillProviderDisposals.set(member.memberId, memberSkills.mountMemberSkillProvider(created.agent.ctx, {
|
|
1114
|
+
skillsDirectory: join(member.privateMemoryPath, 'skills'),
|
|
1115
|
+
bundledSkillsDirectory: BUNDLED_SKILLS_DIRECTORY,
|
|
1116
|
+
selection: skillSelection,
|
|
1117
|
+
}));
|
|
872
1118
|
await workspace.attachSession(member.sessionId);
|
|
873
1119
|
this.handles.set(member.memberId, created);
|
|
874
1120
|
this.modelSelections.set(member.memberId, selected);
|
|
1121
|
+
this.skillSelections.set(member.memberId, skillSelection);
|
|
875
1122
|
this.clearMemberFailure(member.memberId, 'activation');
|
|
876
1123
|
this.nameMemberSession(member, created.agent);
|
|
877
1124
|
this.notifyMember(created.agent);
|
|
@@ -880,6 +1127,9 @@ let AgentTeam = (() => {
|
|
|
880
1127
|
catch (error) {
|
|
881
1128
|
await created?.dispose();
|
|
882
1129
|
this.modelSelections.delete(member.memberId);
|
|
1130
|
+
this.skillSelections.delete(member.memberId);
|
|
1131
|
+
this.releaseMemberToolPolicy(member.memberId);
|
|
1132
|
+
this.releaseMemberSkillProvider(member.memberId);
|
|
883
1133
|
this.setMemberFailure(member.memberId, 'activation', error instanceof Error ? error.message : String(error));
|
|
884
1134
|
}
|
|
885
1135
|
finally {
|
|
@@ -906,6 +1156,9 @@ let AgentTeam = (() => {
|
|
|
906
1156
|
if (stale !== undefined) {
|
|
907
1157
|
this.handles.delete(memberId);
|
|
908
1158
|
this.modelSelections.delete(memberId);
|
|
1159
|
+
this.skillSelections.delete(memberId);
|
|
1160
|
+
this.releaseMemberToolPolicy(memberId);
|
|
1161
|
+
this.releaseMemberSkillProvider(memberId);
|
|
909
1162
|
// The composition-loss diagnostic this heal answers is stale once the
|
|
910
1163
|
// rebuild starts; a later activation must not resurface it.
|
|
911
1164
|
this.clearMemberFailure(memberId, 'compaction');
|
|
@@ -946,8 +1199,79 @@ let AgentTeam = (() => {
|
|
|
946
1199
|
if (missing.length > 0)
|
|
947
1200
|
throw new Error(`team-enabled preset is missing tools: ${missing.join(', ')}`);
|
|
948
1201
|
}
|
|
1202
|
+
/**
|
|
1203
|
+
* Apply one Member's persisted tool allow-list as a scoped restriction on
|
|
1204
|
+
* the freshly composed preset surface, and derive activation-time warnings
|
|
1205
|
+
* for entries the current tool surface no longer knows. Runs inside setup
|
|
1206
|
+
* BEFORE validateMemberPreset so the validation observes the restricted
|
|
1207
|
+
* view (the Host always unions the five Team tools over the configured
|
|
1208
|
+
* list). Deliberate interface reservation: this restriction seam is the
|
|
1209
|
+
* primitive future Runtime Revision manifests orchestrate — do not remove
|
|
1210
|
+
* during cleanup.
|
|
1211
|
+
*/
|
|
1212
|
+
applyMemberToolPolicy(agentCtx, member) {
|
|
1213
|
+
const scope = scopeOf(agentCtx);
|
|
1214
|
+
const configured = member.capabilities?.tools?.allow;
|
|
1215
|
+
if (configured === undefined) {
|
|
1216
|
+
this.capabilityWarnings.delete(member.memberId);
|
|
1217
|
+
return;
|
|
1218
|
+
}
|
|
1219
|
+
// Drop names the current surface does not know rather than failing the
|
|
1220
|
+
// activation: a Harness upgrade renaming a tool must not make the Member
|
|
1221
|
+
// unavailable. The warning carries the known-name digest so a distant
|
|
1222
|
+
// future reader can diagnose the drift.
|
|
1223
|
+
const known = this.ctx.tools.schemas(scope).map(tool => tool.name);
|
|
1224
|
+
const knownSet = new Set(known);
|
|
1225
|
+
const dropped = configured.filter(name => !knownSet.has(name));
|
|
1226
|
+
this.setCapabilityWarnings(member.memberId, dropped.map(name => ({ name, knownNames: known })));
|
|
1227
|
+
const allow = [...new Set([...configured.filter(name => knownSet.has(name)), ...AGENT_TEAM_TOOL_NAMES])];
|
|
1228
|
+
if (allow.length === 0)
|
|
1229
|
+
return;
|
|
1230
|
+
// tools.restrict() requires a scoped context and rejects names outside
|
|
1231
|
+
// the inherited surface; both errors surface as this Member's activation
|
|
1232
|
+
// failure without touching any other Member.
|
|
1233
|
+
const dispose = agentCtx.tools.restrict({ allow });
|
|
1234
|
+
this.memberRestrictions.set(member.memberId, dispose);
|
|
1235
|
+
}
|
|
1236
|
+
/** Swap a live Member's tool policy at a turn boundary: dispose the old restriction, apply the new. */
|
|
1237
|
+
reapplyMemberToolPolicy(member) {
|
|
1238
|
+
this.releaseMemberToolPolicy(member.memberId);
|
|
1239
|
+
this.applyMemberToolPolicy(this.requireLiveMemberContext(member.memberId), member);
|
|
1240
|
+
}
|
|
1241
|
+
requireLiveMemberContext(memberId) {
|
|
1242
|
+
const handle = this.handles.get(memberId);
|
|
1243
|
+
if (handle === undefined)
|
|
1244
|
+
throw new Error(`Agent Member '${this.memberLabel(memberId)}' has no live session for a tool-policy update`);
|
|
1245
|
+
return handle.agent.ctx;
|
|
1246
|
+
}
|
|
1247
|
+
/** Release one Member's restriction disposer and warning state; safe to call twice. */
|
|
1248
|
+
releaseMemberToolPolicy(memberId) {
|
|
1249
|
+
const dispose = this.memberRestrictions.get(memberId);
|
|
1250
|
+
if (dispose !== undefined) {
|
|
1251
|
+
this.memberRestrictions.delete(memberId);
|
|
1252
|
+
dispose();
|
|
1253
|
+
}
|
|
1254
|
+
this.capabilityWarnings.delete(memberId);
|
|
1255
|
+
}
|
|
1256
|
+
/** Release one Member's private skill provider; safe to call twice. */
|
|
1257
|
+
releaseMemberSkillProvider(memberId) {
|
|
1258
|
+
const dispose = this.skillProviderDisposals.get(memberId);
|
|
1259
|
+
if (dispose === undefined)
|
|
1260
|
+
return;
|
|
1261
|
+
this.skillProviderDisposals.delete(memberId);
|
|
1262
|
+
dispose();
|
|
1263
|
+
}
|
|
1264
|
+
setCapabilityWarnings(memberId, warnings) {
|
|
1265
|
+
if (warnings.length === 0)
|
|
1266
|
+
this.capabilityWarnings.delete(memberId);
|
|
1267
|
+
else
|
|
1268
|
+
this.capabilityWarnings.set(memberId, Object.freeze([...warnings]));
|
|
1269
|
+
}
|
|
949
1270
|
async initializePrivateMemory(path) {
|
|
950
1271
|
await mkdir(join(path, 'notes'), { recursive: true });
|
|
1272
|
+
// The Member-private skills directory starts empty; the per-Member
|
|
1273
|
+
// provider scans exactly this root (default roots excluded).
|
|
1274
|
+
await mkdir(join(path, 'skills'), { recursive: true });
|
|
951
1275
|
try {
|
|
952
1276
|
await writeFile(join(path, 'memory.md'), '# Member memory\n\n## Stable facts\n- Add only verified, durable facts that help future work.\n\n## Notes index\n- Add focused `notes/*.md` entries here when a reusable detail needs on-demand reading.\n', { flag: 'wx' });
|
|
953
1277
|
}
|
|
@@ -982,7 +1306,13 @@ let AgentTeam = (() => {
|
|
|
982
1306
|
const runtimeError = failures?.runtime ?? failures?.compaction;
|
|
983
1307
|
if (runtimeError !== undefined)
|
|
984
1308
|
return Object.freeze({ member, availability: 'active', presence: 'error', diagnostic: runtimeError });
|
|
985
|
-
|
|
1309
|
+
// Capability warnings are runtime-derived at activation (handles-scoped,
|
|
1310
|
+
// like failures): absent while capabilities resolve cleanly.
|
|
1311
|
+
const capabilityWarnings = this.capabilityWarnings.get(member.memberId);
|
|
1312
|
+
return Object.freeze({
|
|
1313
|
+
member, availability: 'active', presence: handle.agent.status === 'running' ? 'working' : 'available',
|
|
1314
|
+
...(capabilityWarnings === undefined ? {} : { capabilityWarnings }),
|
|
1315
|
+
});
|
|
986
1316
|
}
|
|
987
1317
|
setMemberFailure(memberId, slot, message) {
|
|
988
1318
|
const failures = this.memberFailures.get(memberId) ?? {};
|
|
@@ -1167,6 +1497,9 @@ let AgentTeam = (() => {
|
|
|
1167
1497
|
this.handles.delete(memberId);
|
|
1168
1498
|
}
|
|
1169
1499
|
this.modelSelections.delete(memberId);
|
|
1500
|
+
this.skillSelections.delete(memberId);
|
|
1501
|
+
this.releaseMemberToolPolicy(memberId);
|
|
1502
|
+
this.releaseMemberSkillProvider(memberId);
|
|
1170
1503
|
this.clearMemberFailure(memberId, 'activation');
|
|
1171
1504
|
}
|
|
1172
1505
|
clearMemberRecoveryState(member) {
|