@wowyuarm/dsh-agent-team 0.1.0
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/LICENSE +21 -0
- package/README.md +103 -0
- package/README.zh.md +103 -0
- package/cordis.patch.yml +36 -0
- package/package.json +152 -0
- package/packages/agent-team/README.md +58 -0
- package/packages/agent-team/README.zh.md +57 -0
- package/packages/agent-team/lib/index.js +841 -0
- package/packages/agent-team/lib/invariant.js +24 -0
- package/packages/agent-team/lib/ledger.js +2161 -0
- package/packages/agent-team/lib/member-context.js +69 -0
- package/packages/agent-team/lib/preset-roster.js +14 -0
- package/packages/agent-team/lib/spec.js +347 -0
- package/packages/agent-team/lib/typert.host.d.ts +3 -0
- package/packages/agent-team/lib/typert.host.js +2084 -0
- package/packages/agent-team/lib/typert.remote-client.d.ts +54 -0
- package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -0
- package/packages/agent-team/lib/typert.remote-client.js +1218 -0
- package/packages/agent-team/lib/types/index.d.ts +156 -0
- package/packages/agent-team/lib/types/index.d.ts.map +1 -0
- package/packages/agent-team/lib/types/invariant.d.ts +13 -0
- package/packages/agent-team/lib/types/invariant.d.ts.map +1 -0
- package/packages/agent-team/lib/types/ledger.d.ts +271 -0
- package/packages/agent-team/lib/types/ledger.d.ts.map +1 -0
- package/packages/agent-team/lib/types/member-context.d.ts +7 -0
- package/packages/agent-team/lib/types/member-context.d.ts.map +1 -0
- package/packages/agent-team/lib/types/preset-roster.d.ts +5 -0
- package/packages/agent-team/lib/types/preset-roster.d.ts.map +1 -0
- package/packages/agent-team/lib/types/spec.d.ts +13 -0
- package/packages/agent-team/lib/types/spec.d.ts.map +1 -0
- package/packages/agent-team/lib/types/types.d.ts +818 -0
- package/packages/agent-team/lib/types/types.d.ts.map +1 -0
- package/packages/agent-team/lib/types.js +1 -0
- package/packages/agent-team/preset/team-member/agent.cordis.yml +89 -0
- package/packages/client-agent-team/README.md +39 -0
- package/packages/client-agent-team/README.zh.md +39 -0
- package/packages/client-agent-team/lib/client.js +9633 -0
- package/packages/client-agent-team/lib/client.js.map +1 -0
- package/packages/client-agent-team/lib/index.js +5 -0
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts +20 -0
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.js +274 -0
- package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts +19 -0
- package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/TeamChannelPage.js +243 -0
- package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.d.ts +21 -0
- package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.js +196 -0
- package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts +15 -0
- package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/TeamComposer.js +144 -0
- package/packages/client-agent-team/lib/types/client/TeamConversation.d.ts +3 -0
- package/packages/client-agent-team/lib/types/client/TeamConversation.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/TeamConversation.js +20 -0
- package/packages/client-agent-team/lib/types/client/TeamFooterAction.d.ts +3 -0
- package/packages/client-agent-team/lib/types/client/TeamFooterAction.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/TeamFooterAction.js +19 -0
- package/packages/client-agent-team/lib/types/client/TeamInboxPanel.d.ts +13 -0
- package/packages/client-agent-team/lib/types/client/TeamInboxPanel.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/TeamInboxPanel.js +90 -0
- package/packages/client-agent-team/lib/types/client/TeamMemberAvatar.d.ts +12 -0
- package/packages/client-agent-team/lib/types/client/TeamMemberAvatar.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/TeamMemberAvatar.js +15 -0
- package/packages/client-agent-team/lib/types/client/TeamMembersAction.d.ts +5 -0
- package/packages/client-agent-team/lib/types/client/TeamMembersAction.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/TeamMembersAction.js +32 -0
- package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts +19 -0
- package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/TeamMessage.js +24 -0
- package/packages/client-agent-team/lib/types/client/TeamPresenceDot.d.ts +11 -0
- package/packages/client-agent-team/lib/types/client/TeamPresenceDot.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/TeamPresenceDot.js +20 -0
- package/packages/client-agent-team/lib/types/client/TeamRowMenu.d.ts +15 -0
- package/packages/client-agent-team/lib/types/client/TeamRowMenu.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/TeamRowMenu.js +23 -0
- package/packages/client-agent-team/lib/types/client/TeamSettings.d.ts +3 -0
- package/packages/client-agent-team/lib/types/client/TeamSettings.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/TeamSettings.js +5 -0
- package/packages/client-agent-team/lib/types/client/TeamSidebarSection.d.ts +9 -0
- package/packages/client-agent-team/lib/types/client/TeamSidebarSection.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/TeamSidebarSection.js +9 -0
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts +22 -0
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.js +464 -0
- package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts +3 -0
- package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.js +39 -0
- package/packages/client-agent-team/lib/types/client/TeamWorkspaceRow.d.ts +11 -0
- package/packages/client-agent-team/lib/types/client/TeamWorkspaceRow.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/TeamWorkspaceRow.js +6 -0
- package/packages/client-agent-team/lib/types/client/index.d.ts +19 -0
- package/packages/client-agent-team/lib/types/client/index.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/index.js +113 -0
- package/packages/client-agent-team/lib/types/client/locales.d.ts +224 -0
- package/packages/client-agent-team/lib/types/client/locales.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/locales.js +222 -0
- package/packages/client-agent-team/lib/types/client/navigation.d.ts +39 -0
- package/packages/client-agent-team/lib/types/client/navigation.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/navigation.js +96 -0
- package/packages/client-agent-team/lib/types/client/slots.d.ts +74 -0
- package/packages/client-agent-team/lib/types/client/slots.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/slots.js +1 -0
- package/packages/client-agent-team/lib/types/client/team-changes.d.ts +28 -0
- package/packages/client-agent-team/lib/types/client/team-changes.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/team-changes.js +70 -0
- package/packages/client-agent-team/lib/types/client/team-formatters.d.ts +53 -0
- package/packages/client-agent-team/lib/types/client/team-formatters.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/team-formatters.js +128 -0
- package/packages/client-agent-team/lib/types/client/team-membership.d.ts +25 -0
- package/packages/client-agent-team/lib/types/client/team-membership.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/team-membership.js +39 -0
- package/packages/client-agent-team/lib/types/client/team-separators.d.ts +23 -0
- package/packages/client-agent-team/lib/types/client/team-separators.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/team-separators.js +52 -0
- package/packages/client-agent-team/lib/types/client/timeline-scroll.d.ts +18 -0
- package/packages/client-agent-team/lib/types/client/timeline-scroll.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/timeline-scroll.js +54 -0
- package/packages/client-agent-team/lib/types/index.d.ts +3 -0
- package/packages/client-agent-team/lib/types/index.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/index.js +2 -0
- package/packages/tool-agent-team/README.md +23 -0
- package/packages/tool-agent-team/README.zh.md +23 -0
- package/packages/tool-agent-team/lib/index.js +294 -0
- package/packages/tool-agent-team/lib/types/index.d.ts +5 -0
- package/packages/tool-agent-team/lib/types/index.d.ts.map +1 -0
|
@@ -0,0 +1,841 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable Agent Team Host capability.
|
|
3
|
+
*
|
|
4
|
+
* The Host owns the append-only collaboration ledger and all Member lifecycle
|
|
5
|
+
* effects. Session history and browser state are projections, never Team facts.
|
|
6
|
+
* @module @wowyuarm/dsh-agent-team
|
|
7
|
+
*/
|
|
8
|
+
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
|
|
9
|
+
var useValue = arguments.length > 2;
|
|
10
|
+
for (var i = 0; i < initializers.length; i++) {
|
|
11
|
+
value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
|
12
|
+
}
|
|
13
|
+
return useValue ? value : void 0;
|
|
14
|
+
};
|
|
15
|
+
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
|
16
|
+
function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
|
|
17
|
+
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
|
18
|
+
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
|
19
|
+
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
|
20
|
+
var _, done = false;
|
|
21
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
22
|
+
var context = {};
|
|
23
|
+
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
|
24
|
+
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
|
25
|
+
context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
|
|
26
|
+
var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
|
|
27
|
+
if (kind === "accessor") {
|
|
28
|
+
if (result === void 0) continue;
|
|
29
|
+
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
|
30
|
+
if (_ = accept(result.get)) descriptor.get = _;
|
|
31
|
+
if (_ = accept(result.set)) descriptor.set = _;
|
|
32
|
+
if (_ = accept(result.init)) initializers.unshift(_);
|
|
33
|
+
}
|
|
34
|
+
else if (_ = accept(result)) {
|
|
35
|
+
if (kind === "field") initializers.unshift(_);
|
|
36
|
+
else descriptor[key] = _;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
40
|
+
done = true;
|
|
41
|
+
};
|
|
42
|
+
import { randomUUID } from 'node:crypto';
|
|
43
|
+
import { mkdir, rm, writeFile } from 'node:fs/promises';
|
|
44
|
+
import { join } from 'node:path';
|
|
45
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
46
|
+
import { Service } from '@deepseek-ai/cordis';
|
|
47
|
+
import { createUserMessage } from '@deepseek-ai/dsh-llm';
|
|
48
|
+
import { dshHomePath } from '@deepseek-ai/dsh-home-paths';
|
|
49
|
+
import { scopeOf } from '@deepseek-ai/dsh-scope';
|
|
50
|
+
import { SessionId } from '@deepseek-ai/dsh-session';
|
|
51
|
+
import { effectiveSandboxMode, setSandboxMode } from '@deepseek-ai/dsh-sandbox-policy';
|
|
52
|
+
import { Remote, TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
|
|
53
|
+
import { AGENT_TEAM_HUMAN_MEMBER_ID, AgentTeamLedger, agentTeamHumanActor } from "./ledger.js";
|
|
54
|
+
import { agentTeamDomainSpec } from "./spec.js";
|
|
55
|
+
export { agentTeamDomainSpec, agentTeamOperationSchema } from "./spec.js";
|
|
56
|
+
export { AGENT_TEAM_HUMAN_MEMBER_ID, AGENT_TEAM_INITIALIZE_REQUEST_ID } from "./ledger.js";
|
|
57
|
+
/** Process-stable marker carried by the final Team message tool definition. */
|
|
58
|
+
export const AGENT_TEAM_PRESET_MARKER = Symbol.for('@wowyuarm/dsh-agent-team.preset');
|
|
59
|
+
function sameChangeScope(left, right) {
|
|
60
|
+
if (left.kind === 'workspace' && right.kind === 'workspace')
|
|
61
|
+
return left.workspaceId === right.workspaceId;
|
|
62
|
+
if (left.kind === 'channel' && right.kind === 'channel')
|
|
63
|
+
return left.channelRef === right.channelRef;
|
|
64
|
+
if (left.kind === 'thread' && right.kind === 'thread')
|
|
65
|
+
return left.threadRef === right.threadRef;
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
/** Mark the preset's `team_message` definition as an Agent Team consumer. */
|
|
69
|
+
export function markAgentTeamPreset(definition) {
|
|
70
|
+
Object.defineProperty(definition, AGENT_TEAM_PRESET_MARKER, { value: true });
|
|
71
|
+
return definition;
|
|
72
|
+
}
|
|
73
|
+
/** Model-facing capabilities every Team-enabled preset must publish. */
|
|
74
|
+
export const AGENT_TEAM_TOOL_NAMES = Object.freeze([
|
|
75
|
+
'team_inbox',
|
|
76
|
+
'team_thread',
|
|
77
|
+
'team_message',
|
|
78
|
+
'team_claim',
|
|
79
|
+
'team_view',
|
|
80
|
+
]);
|
|
81
|
+
let AgentTeam = (() => {
|
|
82
|
+
let _classSuper = TypertRemoteService;
|
|
83
|
+
let _instanceExtraInitializers = [];
|
|
84
|
+
let _membersForClient_decorators;
|
|
85
|
+
let _changes_decorators;
|
|
86
|
+
let _createChannel_decorators;
|
|
87
|
+
let _updateChannel_decorators;
|
|
88
|
+
let _addMember_decorators;
|
|
89
|
+
let _updateMember_decorators;
|
|
90
|
+
let _changeTask_decorators;
|
|
91
|
+
let _joinChannel_decorators;
|
|
92
|
+
let _removeChannelMember_decorators;
|
|
93
|
+
let _sendMessage_decorators;
|
|
94
|
+
let _reply_decorators;
|
|
95
|
+
let _changeAttention_decorators;
|
|
96
|
+
let _inbox_decorators;
|
|
97
|
+
let _readThread_decorators;
|
|
98
|
+
let _threadObservations_decorators;
|
|
99
|
+
let _threadHistory_decorators;
|
|
100
|
+
let _view_decorators;
|
|
101
|
+
return class AgentTeam extends _classSuper {
|
|
102
|
+
static {
|
|
103
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
104
|
+
_membersForClient_decorators = [Remote('members')];
|
|
105
|
+
_changes_decorators = [Remote('changes')];
|
|
106
|
+
_createChannel_decorators = [Remote('createChannel')];
|
|
107
|
+
_updateChannel_decorators = [Remote('updateChannel')];
|
|
108
|
+
_addMember_decorators = [Remote('addMember')];
|
|
109
|
+
_updateMember_decorators = [Remote('updateMember')];
|
|
110
|
+
_changeTask_decorators = [Remote('changeTask')];
|
|
111
|
+
_joinChannel_decorators = [Remote('joinChannel')];
|
|
112
|
+
_removeChannelMember_decorators = [Remote('removeChannelMember')];
|
|
113
|
+
_sendMessage_decorators = [Remote('sendMessage')];
|
|
114
|
+
_reply_decorators = [Remote('reply')];
|
|
115
|
+
_changeAttention_decorators = [Remote('changeAttention')];
|
|
116
|
+
_inbox_decorators = [Remote('inbox')];
|
|
117
|
+
_readThread_decorators = [Remote('readThread')];
|
|
118
|
+
_threadObservations_decorators = [Remote('threadObservations')];
|
|
119
|
+
_threadHistory_decorators = [Remote('threadHistory')];
|
|
120
|
+
_view_decorators = [Remote('view')];
|
|
121
|
+
__esDecorate(this, null, _membersForClient_decorators, { kind: "method", name: "membersForClient", static: false, private: false, access: { has: obj => "membersForClient" in obj, get: obj => obj.membersForClient }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
122
|
+
__esDecorate(this, null, _changes_decorators, { kind: "method", name: "changes", static: false, private: false, access: { has: obj => "changes" in obj, get: obj => obj.changes }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
123
|
+
__esDecorate(this, null, _createChannel_decorators, { kind: "method", name: "createChannel", static: false, private: false, access: { has: obj => "createChannel" in obj, get: obj => obj.createChannel }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
124
|
+
__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);
|
|
125
|
+
__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);
|
|
126
|
+
__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);
|
|
127
|
+
__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);
|
|
128
|
+
__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);
|
|
129
|
+
__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);
|
|
130
|
+
__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);
|
|
131
|
+
__esDecorate(this, null, _reply_decorators, { kind: "method", name: "reply", static: false, private: false, access: { has: obj => "reply" in obj, get: obj => obj.reply }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
132
|
+
__esDecorate(this, null, _changeAttention_decorators, { kind: "method", name: "changeAttention", static: false, private: false, access: { has: obj => "changeAttention" in obj, get: obj => obj.changeAttention }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
133
|
+
__esDecorate(this, null, _inbox_decorators, { kind: "method", name: "inbox", static: false, private: false, access: { has: obj => "inbox" in obj, get: obj => obj.inbox }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
134
|
+
__esDecorate(this, null, _readThread_decorators, { kind: "method", name: "readThread", static: false, private: false, access: { has: obj => "readThread" in obj, get: obj => obj.readThread }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
135
|
+
__esDecorate(this, null, _threadObservations_decorators, { kind: "method", name: "threadObservations", static: false, private: false, access: { has: obj => "threadObservations" in obj, get: obj => obj.threadObservations }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
136
|
+
__esDecorate(this, null, _threadHistory_decorators, { kind: "method", name: "threadHistory", static: false, private: false, access: { has: obj => "threadHistory" in obj, get: obj => obj.threadHistory }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
137
|
+
__esDecorate(this, null, _view_decorators, { kind: "method", name: "view", static: false, private: false, access: { has: obj => "view" in obj, get: obj => obj.view }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
138
|
+
if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
139
|
+
}
|
|
140
|
+
static inject = [
|
|
141
|
+
'storageDomain',
|
|
142
|
+
'workspaceRegistry',
|
|
143
|
+
'agents',
|
|
144
|
+
'agentDefaultModel',
|
|
145
|
+
'agentPresets',
|
|
146
|
+
'tools',
|
|
147
|
+
'sessionPersistence',
|
|
148
|
+
];
|
|
149
|
+
domain = __runInitializers(this, _instanceExtraInitializers);
|
|
150
|
+
ledger;
|
|
151
|
+
handles = new Map();
|
|
152
|
+
diagnostics = new Map();
|
|
153
|
+
runtimeErrors = new Map();
|
|
154
|
+
notifiedInbox = new Map();
|
|
155
|
+
lifecycleTail = Promise.resolve();
|
|
156
|
+
accepting = true;
|
|
157
|
+
changeVersion = 0;
|
|
158
|
+
changeWaiters = new Set();
|
|
159
|
+
constructor(ctx) {
|
|
160
|
+
super(ctx, 'agentTeam');
|
|
161
|
+
}
|
|
162
|
+
/** Open the durable ledger and restore every enabled Member independently. */
|
|
163
|
+
async [Service.init]() {
|
|
164
|
+
this.ctx.on('agent/error', ({ agent, error }) => {
|
|
165
|
+
const member = this.memberForAgent(agent);
|
|
166
|
+
if (member === undefined)
|
|
167
|
+
return;
|
|
168
|
+
this.runtimeErrors.set(agent.id, error instanceof Error ? error.message : String(error));
|
|
169
|
+
this.emitChanged([{ kind: 'workspace', workspaceId: member.workspaceId }]);
|
|
170
|
+
});
|
|
171
|
+
this.ctx.on('agent/status', ({ agent, status }) => {
|
|
172
|
+
const member = this.memberForAgent(agent);
|
|
173
|
+
if (status === 'running' && member !== undefined) {
|
|
174
|
+
const recovered = this.runtimeErrors.delete(agent.id);
|
|
175
|
+
if (recovered)
|
|
176
|
+
this.notifiedInbox.delete(member.memberId);
|
|
177
|
+
this.emitChanged([{ kind: 'workspace', workspaceId: member.workspaceId }]);
|
|
178
|
+
this.notifyMember(agent);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
const domain = await this.ctx.storageDomain.open(agentTeamDomainSpec);
|
|
182
|
+
this.ctx.effect(() => async () => {
|
|
183
|
+
this.accepting = false;
|
|
184
|
+
this.emitChanged();
|
|
185
|
+
await this.lifecycleTail;
|
|
186
|
+
await Promise.all([...this.handles.values()].map(handle => handle.dispose()));
|
|
187
|
+
this.handles.clear();
|
|
188
|
+
await domain.close();
|
|
189
|
+
}, 'agentTeam.dispose');
|
|
190
|
+
this.domain = domain;
|
|
191
|
+
const ledger = new AgentTeamLedger(domain.table('operations'));
|
|
192
|
+
this.ledger = ledger;
|
|
193
|
+
const initialization = await ledger.initialize();
|
|
194
|
+
if (initialization.committed)
|
|
195
|
+
this.emitCommitted(initialization.value);
|
|
196
|
+
// One metadata listing serves every Member restore; per-member list calls
|
|
197
|
+
// would repeat the same I/O linearly during startup.
|
|
198
|
+
const persistedSessions = new Set((await this.ctx.sessionPersistence.list()).map(header => header.id));
|
|
199
|
+
for (const member of ledger.listMembers()) {
|
|
200
|
+
if (member.state === 'enabled')
|
|
201
|
+
await this.activateMember(member, undefined, persistedSessions);
|
|
202
|
+
else if (member.state === 'inactive')
|
|
203
|
+
await this.cleanupRemovedMember(member);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
/** Resolve one exact live Agent to its durable Team Member; forks do not inherit identity. */
|
|
207
|
+
memberForAgent(agent) {
|
|
208
|
+
for (const [memberId, handle] of this.handles) {
|
|
209
|
+
if (handle.agent === agent)
|
|
210
|
+
return this.requireLedger().getMember(memberId);
|
|
211
|
+
}
|
|
212
|
+
return undefined;
|
|
213
|
+
}
|
|
214
|
+
/** Return every durable Member with current process availability. */
|
|
215
|
+
members() {
|
|
216
|
+
return this.requireLedger().listMembers().map(member => this.memberStatus(member));
|
|
217
|
+
}
|
|
218
|
+
/** Return only this Workspace's current Member projection to the Client. */
|
|
219
|
+
membersForClient(request) {
|
|
220
|
+
this.requireWorkspace(request.workspaceId);
|
|
221
|
+
return this.members()
|
|
222
|
+
.filter(status => status.member.workspaceId === request.workspaceId)
|
|
223
|
+
.map(({ member: { privateMemoryPath: _privateMemoryPath, ...member }, ...status }) => Object.freeze({ ...status, member: Object.freeze(member) }));
|
|
224
|
+
}
|
|
225
|
+
/** Wait for a lightweight projection invalidation without exposing ledger records. */
|
|
226
|
+
async changes(request, signal) {
|
|
227
|
+
if (!Number.isInteger(request.afterVersion) || request.afterVersion < 0)
|
|
228
|
+
throw new Error('afterVersion must be a non-negative integer');
|
|
229
|
+
const scope = this.validateChangeScope(request.scope);
|
|
230
|
+
if (this.changeVersion > request.afterVersion || !this.accepting)
|
|
231
|
+
return Object.freeze({ version: this.changeVersion });
|
|
232
|
+
return new Promise((resolve, reject) => {
|
|
233
|
+
let settled = false;
|
|
234
|
+
const waiter = {
|
|
235
|
+
scope,
|
|
236
|
+
wake: version => {
|
|
237
|
+
if (settled)
|
|
238
|
+
return;
|
|
239
|
+
settled = true;
|
|
240
|
+
clearTimeout(timeout);
|
|
241
|
+
signal?.removeEventListener('abort', onAbort);
|
|
242
|
+
resolve(Object.freeze({ version }));
|
|
243
|
+
},
|
|
244
|
+
};
|
|
245
|
+
const timeout = setTimeout(() => {
|
|
246
|
+
if (settled)
|
|
247
|
+
return;
|
|
248
|
+
settled = true;
|
|
249
|
+
this.changeWaiters.delete(waiter);
|
|
250
|
+
resolve(Object.freeze({ version: this.changeVersion }));
|
|
251
|
+
}, 25_000);
|
|
252
|
+
const onAbort = () => {
|
|
253
|
+
if (settled)
|
|
254
|
+
return;
|
|
255
|
+
settled = true;
|
|
256
|
+
clearTimeout(timeout);
|
|
257
|
+
this.changeWaiters.delete(waiter);
|
|
258
|
+
reject(new Error('changes wait was aborted'));
|
|
259
|
+
};
|
|
260
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
261
|
+
if (signal?.aborted === true) {
|
|
262
|
+
onAbort();
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
this.changeWaiters.add(waiter);
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
/** Return durable Team status without issuing a model request or a storage write. */
|
|
269
|
+
status() {
|
|
270
|
+
return this.requireLedger().status();
|
|
271
|
+
}
|
|
272
|
+
async createChannel(request) {
|
|
273
|
+
this.requireAccepting();
|
|
274
|
+
this.requireWorkspace(request.workspaceId);
|
|
275
|
+
const ledger = this.requireLedger();
|
|
276
|
+
if (!ledger.hasCommitted(request.requestId))
|
|
277
|
+
this.assertChannelMembersAvailable(request.memberIds);
|
|
278
|
+
const result = await ledger.createChannel({ ...request, actor: agentTeamHumanActor() });
|
|
279
|
+
if (result.committed)
|
|
280
|
+
this.emitCommitted(result.value.receipt);
|
|
281
|
+
return result.value;
|
|
282
|
+
}
|
|
283
|
+
/** Human rename of one Channel's display facts; identity refs are immutable. */
|
|
284
|
+
async updateChannel(request) {
|
|
285
|
+
this.requireAccepting();
|
|
286
|
+
this.requireWorkspace(request.workspaceId);
|
|
287
|
+
const result = await this.requireLedger().updateChannel({ ...request, actor: agentTeamHumanActor() });
|
|
288
|
+
if (result.committed)
|
|
289
|
+
this.emitCommitted(result.value.receipt);
|
|
290
|
+
return result.value;
|
|
291
|
+
}
|
|
292
|
+
/** Create a durable Member and atomically grant its declared initial Channels. */
|
|
293
|
+
async addMember(request) {
|
|
294
|
+
return this.enqueueLifecycle(async () => {
|
|
295
|
+
const workspace = this.requireWorkspace(request.workspaceId);
|
|
296
|
+
const memberId = `member:${randomUUID()}`;
|
|
297
|
+
const member = Object.freeze({
|
|
298
|
+
memberId,
|
|
299
|
+
sessionId: SessionId(`agent-team-${randomUUID()}`),
|
|
300
|
+
workspaceId: request.workspaceId,
|
|
301
|
+
handle: request.handle,
|
|
302
|
+
description: request.description,
|
|
303
|
+
presetId: request.presetId,
|
|
304
|
+
...(request.model === undefined ? {} : { model: Object.freeze({ ...request.model }) }),
|
|
305
|
+
privateMemoryPath: dshHomePath('agent-team', 'members', memberId),
|
|
306
|
+
state: 'enabled',
|
|
307
|
+
});
|
|
308
|
+
const result = await this.requireLedger().addMember({ ...request, actor: agentTeamHumanActor(), member });
|
|
309
|
+
if (result.committed)
|
|
310
|
+
this.emitCommitted(result.value.receipt);
|
|
311
|
+
const stored = result.value.member;
|
|
312
|
+
if (!this.handles.has(stored.memberId))
|
|
313
|
+
await this.activateMember(stored, workspace.path);
|
|
314
|
+
return Object.freeze({ receipt: result.value.receipt, status: this.memberStatus(stored) });
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
/** Commit suspended intent, then wait for the owned AgentHandle to become quiescent. */
|
|
318
|
+
async suspendMember(request) {
|
|
319
|
+
return this.enqueueLifecycle(async () => {
|
|
320
|
+
const result = await this.requireLedger().suspendMember({ ...request, actor: agentTeamHumanActor() });
|
|
321
|
+
if (result.committed)
|
|
322
|
+
this.emitCommitted(result.value.receipt);
|
|
323
|
+
const handle = this.handles.get(request.memberId);
|
|
324
|
+
if (handle !== undefined) {
|
|
325
|
+
await handle.dispose();
|
|
326
|
+
this.handles.delete(request.memberId);
|
|
327
|
+
}
|
|
328
|
+
this.diagnostics.delete(request.memberId);
|
|
329
|
+
this.clearMemberNotificationState(request.memberId);
|
|
330
|
+
return Object.freeze({ receipt: result.value.receipt, status: this.memberStatus(result.value.member) });
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
/** Commit enabled intent and restore the exact persisted Session. */
|
|
334
|
+
async resumeMember(request) {
|
|
335
|
+
return this.enqueueLifecycle(async () => {
|
|
336
|
+
const result = await this.requireLedger().resumeMember({ ...request, actor: agentTeamHumanActor() });
|
|
337
|
+
if (result.committed)
|
|
338
|
+
this.emitCommitted(result.value.receipt);
|
|
339
|
+
this.clearMemberNotificationState(result.value.member.memberId);
|
|
340
|
+
await this.activateMember(result.value.member);
|
|
341
|
+
return Object.freeze({ receipt: result.value.receipt, status: this.memberStatus(result.value.member) });
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Human edit of one Member's mutable facts. A model change restarts an
|
|
346
|
+
* active Member: single-Session Members read their selection only at
|
|
347
|
+
* activation, so the quiescing dispose plus reactivation applies the new
|
|
348
|
+
* provider/model immediately instead of at the next Host restart.
|
|
349
|
+
*/
|
|
350
|
+
async updateMember(request) {
|
|
351
|
+
return this.enqueueLifecycle(async () => {
|
|
352
|
+
const previous = this.requireLedger().getMember(request.memberId);
|
|
353
|
+
const result = await this.requireLedger().updateMember({ ...request, actor: agentTeamHumanActor() });
|
|
354
|
+
if (result.committed)
|
|
355
|
+
this.emitCommitted(result.value.receipt);
|
|
356
|
+
const stored = result.value.member;
|
|
357
|
+
const active = this.handles.get(request.memberId);
|
|
358
|
+
if (active !== undefined && !isDeepStrictEqual(previous?.model ?? undefined, stored.model ?? undefined)) {
|
|
359
|
+
await active.dispose();
|
|
360
|
+
this.handles.delete(request.memberId);
|
|
361
|
+
await this.activateMember(stored);
|
|
362
|
+
}
|
|
363
|
+
return Object.freeze({ receipt: result.value.receipt, status: this.memberStatus(stored) });
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
/** Irreversibly remove one Member, archive its Session, and delete its private namespace. */
|
|
367
|
+
async removeMember(request) {
|
|
368
|
+
return this.enqueueLifecycle(async () => {
|
|
369
|
+
const result = await this.requireLedger().removeMember({ ...request, actor: agentTeamHumanActor() });
|
|
370
|
+
if (result.committed)
|
|
371
|
+
this.emitCommitted(result.value.receipt);
|
|
372
|
+
const handle = this.handles.get(request.memberId);
|
|
373
|
+
if (handle !== undefined) {
|
|
374
|
+
await handle.dispose();
|
|
375
|
+
this.handles.delete(request.memberId);
|
|
376
|
+
}
|
|
377
|
+
this.diagnostics.delete(request.memberId);
|
|
378
|
+
this.clearMemberNotificationState(request.memberId);
|
|
379
|
+
await this.cleanupRemovedMember(result.value.member);
|
|
380
|
+
return result.value;
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
/** Human-only Task resolution. Business fences are returned as typed outcomes. */
|
|
384
|
+
async changeTask(request) {
|
|
385
|
+
this.requireAccepting();
|
|
386
|
+
this.requireWorkspace(request.workspaceId);
|
|
387
|
+
const result = await this.requireLedger().changeTask({ ...request, actor: agentTeamHumanActor() });
|
|
388
|
+
this.emitCommittedOutcome(result);
|
|
389
|
+
return result.value;
|
|
390
|
+
}
|
|
391
|
+
/** Human-only Channel membership grant; it never injects historical Thread bodies. */
|
|
392
|
+
async joinChannel(request) {
|
|
393
|
+
this.requireAccepting();
|
|
394
|
+
this.requireWorkspace(request.workspaceId);
|
|
395
|
+
const ledger = this.requireLedger();
|
|
396
|
+
if (!ledger.hasCommitted(request.requestId))
|
|
397
|
+
this.assertChannelMembersAvailable([request.memberId]);
|
|
398
|
+
const result = await ledger.joinChannel({ ...request, actor: agentTeamHumanActor() });
|
|
399
|
+
if (result.committed)
|
|
400
|
+
this.emitCommitted(result.value.receipt);
|
|
401
|
+
return result.value;
|
|
402
|
+
}
|
|
403
|
+
/** Human-only Channel membership removal and Channel-scoped cleanup. */
|
|
404
|
+
async removeChannelMember(request) {
|
|
405
|
+
this.requireAccepting();
|
|
406
|
+
this.requireWorkspace(request.workspaceId);
|
|
407
|
+
const result = await this.requireLedger().removeChannelMember({ ...request, actor: agentTeamHumanActor() });
|
|
408
|
+
if (result.committed)
|
|
409
|
+
this.emitCommitted(result.value.receipt);
|
|
410
|
+
return result.value;
|
|
411
|
+
}
|
|
412
|
+
/** Human top-level Task creation; unfollowed Agent mentions use the two-send result. */
|
|
413
|
+
async sendMessage(request) {
|
|
414
|
+
this.requireAccepting();
|
|
415
|
+
this.requireWorkspace(request.workspaceId);
|
|
416
|
+
const result = await this.requireLedger().sendMessage({ ...request, actor: agentTeamHumanActor() });
|
|
417
|
+
this.emitCommittedOutcome(result);
|
|
418
|
+
return result.value;
|
|
419
|
+
}
|
|
420
|
+
/** Human existing-Thread reply; unread and revision conflicts are business outcomes. */
|
|
421
|
+
async reply(request) {
|
|
422
|
+
this.requireAccepting();
|
|
423
|
+
this.requireWorkspace(request.workspaceId);
|
|
424
|
+
const result = await this.requireLedger().reply({ ...request, actor: agentTeamHumanActor() });
|
|
425
|
+
this.emitCommittedOutcome(result);
|
|
426
|
+
return result.value;
|
|
427
|
+
}
|
|
428
|
+
/** Human's personal Attention operation. */
|
|
429
|
+
async changeAttention(request) {
|
|
430
|
+
this.requireAccepting();
|
|
431
|
+
this.requireWorkspace(request.workspaceId);
|
|
432
|
+
const result = await this.requireLedger().changeAttention({ ...request, actor: agentTeamHumanActor() });
|
|
433
|
+
if (result.committed)
|
|
434
|
+
this.emitCommitted(result.value.receipt);
|
|
435
|
+
return result.value;
|
|
436
|
+
}
|
|
437
|
+
/** Host-only Human Inbox projection; the Web Client does not consume it. */
|
|
438
|
+
inbox(request) {
|
|
439
|
+
this.requireWorkspace(request.workspaceId);
|
|
440
|
+
return this.requireLedger().inbox(agentTeamHumanActor(), request);
|
|
441
|
+
}
|
|
442
|
+
/** Human's durable, atomically acknowledged Thread read. */
|
|
443
|
+
async readThread(request) {
|
|
444
|
+
this.requireAccepting();
|
|
445
|
+
this.requireWorkspace(request.workspaceId);
|
|
446
|
+
const result = await this.requireLedger().readThread({ ...request, actor: agentTeamHumanActor() });
|
|
447
|
+
if (result.committed)
|
|
448
|
+
this.emitCommitted(result.value.receipt);
|
|
449
|
+
return result.value;
|
|
450
|
+
}
|
|
451
|
+
/** Human-only durable Attention observations for one Thread. */
|
|
452
|
+
threadObservations(request) {
|
|
453
|
+
this.requireWorkspace(request.workspaceId);
|
|
454
|
+
return this.requireLedger().threadObservations(agentTeamHumanActor(), request);
|
|
455
|
+
}
|
|
456
|
+
/** Human's non-mutating bounded Thread history. */
|
|
457
|
+
threadHistory(request) {
|
|
458
|
+
this.requireWorkspace(request.workspaceId);
|
|
459
|
+
return this.requireLedger().threadHistory(agentTeamHumanActor(), request);
|
|
460
|
+
}
|
|
461
|
+
/** Return the existing bounded public Workspace discovery projection. */
|
|
462
|
+
view(request) {
|
|
463
|
+
this.requireWorkspace(request.workspaceId);
|
|
464
|
+
return this.requireLedger().view(request);
|
|
465
|
+
}
|
|
466
|
+
/** Agent-only top-level Task creation. Workspace identity is verified against the live binding. */
|
|
467
|
+
async sendMessageForAgent(agent, request) {
|
|
468
|
+
this.requireAccepting();
|
|
469
|
+
const actor = this.memberActor(agent);
|
|
470
|
+
this.requireAgentWorkspace(actor, request.workspaceId);
|
|
471
|
+
const result = await this.requireLedger().sendMessage({ ...request, actor });
|
|
472
|
+
this.emitCommittedOutcome(result);
|
|
473
|
+
return result.value;
|
|
474
|
+
}
|
|
475
|
+
/** Agent-only existing-Thread reply. */
|
|
476
|
+
async replyForAgent(agent, request) {
|
|
477
|
+
this.requireAccepting();
|
|
478
|
+
const actor = this.memberActor(agent);
|
|
479
|
+
this.requireAgentWorkspace(actor, request.workspaceId);
|
|
480
|
+
const result = await this.requireLedger().reply({ ...request, actor });
|
|
481
|
+
this.emitCommittedOutcome(result);
|
|
482
|
+
return result.value;
|
|
483
|
+
}
|
|
484
|
+
/** Agent-only personal Attention change. */
|
|
485
|
+
async changeAttentionForAgent(agent, request) {
|
|
486
|
+
this.requireAccepting();
|
|
487
|
+
const actor = this.memberActor(agent);
|
|
488
|
+
this.requireAgentWorkspace(actor, request.workspaceId);
|
|
489
|
+
const result = await this.requireLedger().changeAttention({ ...request, actor });
|
|
490
|
+
if (result.committed)
|
|
491
|
+
this.emitCommitted(result.value.receipt);
|
|
492
|
+
return result.value;
|
|
493
|
+
}
|
|
494
|
+
attentionStatusForAgent(agent, request) {
|
|
495
|
+
const actor = this.memberActor(agent);
|
|
496
|
+
this.requireAgentWorkspace(actor, request.workspaceId);
|
|
497
|
+
return this.requireLedger().attentionStatus(actor, request);
|
|
498
|
+
}
|
|
499
|
+
/** Agent-only Claim mutation. */
|
|
500
|
+
async changeClaimForAgent(agent, request) {
|
|
501
|
+
this.requireAccepting();
|
|
502
|
+
const actor = this.memberActor(agent);
|
|
503
|
+
this.requireAgentWorkspace(actor, request.workspaceId);
|
|
504
|
+
const result = await this.requireLedger().changeClaim({ ...request, actor });
|
|
505
|
+
this.emitCommittedOutcome(result);
|
|
506
|
+
return result.value;
|
|
507
|
+
}
|
|
508
|
+
listClaimsForAgent(agent, request) {
|
|
509
|
+
const actor = this.memberActor(agent);
|
|
510
|
+
this.requireAgentWorkspace(actor, request.workspaceId);
|
|
511
|
+
return this.requireLedger().listClaims(actor, request);
|
|
512
|
+
}
|
|
513
|
+
inboxForAgent(agent, request) {
|
|
514
|
+
const actor = this.memberActor(agent);
|
|
515
|
+
this.requireAgentWorkspace(actor, request.workspaceId);
|
|
516
|
+
return this.requireLedger().inbox(actor, request);
|
|
517
|
+
}
|
|
518
|
+
async readThreadForAgent(agent, request) {
|
|
519
|
+
this.requireAccepting();
|
|
520
|
+
const actor = this.memberActor(agent);
|
|
521
|
+
this.requireAgentWorkspace(actor, request.workspaceId);
|
|
522
|
+
const result = await this.requireLedger().readThread({ ...request, actor });
|
|
523
|
+
if (result.committed)
|
|
524
|
+
this.emitCommitted(result.value.receipt);
|
|
525
|
+
return result.value;
|
|
526
|
+
}
|
|
527
|
+
threadHistoryForAgent(agent, request) {
|
|
528
|
+
const actor = this.memberActor(agent);
|
|
529
|
+
this.requireAgentWorkspace(actor, request.workspaceId);
|
|
530
|
+
return this.requireLedger().threadHistory(actor, request);
|
|
531
|
+
}
|
|
532
|
+
/** Agent-only bounded discovery projection. */
|
|
533
|
+
viewForAgent(agent, request) {
|
|
534
|
+
const member = this.memberForAgent(agent);
|
|
535
|
+
if (member === undefined)
|
|
536
|
+
throw new Error('Agent is not an active Team Member');
|
|
537
|
+
if (member.workspaceId !== request.workspaceId)
|
|
538
|
+
throw new Error('Member cannot view another Workspace');
|
|
539
|
+
return this.requireLedger().view(request, member.memberId);
|
|
540
|
+
}
|
|
541
|
+
/** Validate the durable ledger against an independently replayed projection. */
|
|
542
|
+
validateLedger() {
|
|
543
|
+
this.requireLedger().validate();
|
|
544
|
+
}
|
|
545
|
+
emitCommittedOutcome(result) {
|
|
546
|
+
if (result.committed && result.value.kind === 'committed' && result.value.receipt !== undefined)
|
|
547
|
+
this.emitCommitted(result.value.receipt);
|
|
548
|
+
}
|
|
549
|
+
assertChannelMembersAvailable(memberIds) {
|
|
550
|
+
for (const memberId of memberIds ?? []) {
|
|
551
|
+
const member = this.requireLedger().getMember(memberId);
|
|
552
|
+
if (member === undefined)
|
|
553
|
+
throw new Error(`unknown Agent Member '${memberId}'`);
|
|
554
|
+
if (this.memberStatus(member).availability !== 'active')
|
|
555
|
+
throw new Error(`Agent Member '${memberId}' is not available for Channel membership`);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
memberActor(agent) {
|
|
559
|
+
const member = this.memberForAgent(agent);
|
|
560
|
+
if (member === undefined)
|
|
561
|
+
throw new Error('Agent is not an active Team Member');
|
|
562
|
+
return Object.freeze({ kind: 'member', memberId: member.memberId, handle: member.handle });
|
|
563
|
+
}
|
|
564
|
+
requireAgentWorkspace(actor, workspaceId) {
|
|
565
|
+
if (this.requireLedger().getMember(actor.memberId)?.workspaceId !== workspaceId)
|
|
566
|
+
throw new Error('Member cannot mutate another Workspace');
|
|
567
|
+
}
|
|
568
|
+
async activateMember(member, knownWorkspacePath, knownSessions) {
|
|
569
|
+
if (this.handles.has(member.memberId))
|
|
570
|
+
return;
|
|
571
|
+
let created;
|
|
572
|
+
try {
|
|
573
|
+
const workspace = this.requireWorkspace(member.workspaceId);
|
|
574
|
+
const workspacePath = knownWorkspacePath ?? workspace.path;
|
|
575
|
+
await this.initializePrivateMemory(member.privateMemoryPath);
|
|
576
|
+
const persisted = knownSessions !== undefined ? knownSessions.has(member.sessionId)
|
|
577
|
+
: (await this.ctx.sessionPersistence.list()).some(header => header.id === member.sessionId);
|
|
578
|
+
const setup = async (agentCtx) => {
|
|
579
|
+
await this.ctx.agentPresets.mount(agentCtx, member.presetId);
|
|
580
|
+
this.validateMemberPreset(agentCtx);
|
|
581
|
+
return {
|
|
582
|
+
commit: () => {
|
|
583
|
+
const agent = agentCtx.agent;
|
|
584
|
+
if (agent === undefined)
|
|
585
|
+
throw new Error('agent-team setup has no unpublished Agent');
|
|
586
|
+
if (effectiveSandboxMode(agent.session.events) !== 'danger-full-access')
|
|
587
|
+
setSandboxMode(agent.session, 'danger-full-access');
|
|
588
|
+
},
|
|
589
|
+
};
|
|
590
|
+
};
|
|
591
|
+
// A Member-pinned selection wins; otherwise the Host default applies at
|
|
592
|
+
// every activation, so an override survives restarts and a cleared one
|
|
593
|
+
// re-reads the live default.
|
|
594
|
+
const agentOptions = member.model ?? this.ctx.agentDefaultModel.currentSelection();
|
|
595
|
+
created = persisted
|
|
596
|
+
? await this.ctx.agents.resume({ resumeSessionId: member.sessionId, agentOptions, setup })
|
|
597
|
+
: await this.ctx.agents.create({
|
|
598
|
+
sessionId: member.sessionId,
|
|
599
|
+
meta: { cwd: workspacePath, agentPreset: member.presetId },
|
|
600
|
+
agentOptions,
|
|
601
|
+
setup,
|
|
602
|
+
});
|
|
603
|
+
await workspace.attachSession(member.sessionId);
|
|
604
|
+
this.handles.set(member.memberId, created);
|
|
605
|
+
this.diagnostics.delete(member.memberId);
|
|
606
|
+
this.nameMemberSession(member, created.agent);
|
|
607
|
+
this.notifyMember(created.agent);
|
|
608
|
+
}
|
|
609
|
+
catch (error) {
|
|
610
|
+
await created?.dispose();
|
|
611
|
+
this.diagnostics.set(member.memberId, error instanceof Error ? error.message : String(error));
|
|
612
|
+
}
|
|
613
|
+
finally {
|
|
614
|
+
// Activation only changes this Workspace's presence projection.
|
|
615
|
+
this.emitChanged([{ kind: 'workspace', workspaceId: member.workspaceId }]);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
619
|
+
* Default an untitled Member Session to its handle so the ordinary Session
|
|
620
|
+
* list names it. An explicit rename or any earlier title always wins; the
|
|
621
|
+
* cosmetic default never fails Member activation.
|
|
622
|
+
*/
|
|
623
|
+
nameMemberSession(member, agent) {
|
|
624
|
+
const sessionTitle = this.ctx.get('sessionTitle');
|
|
625
|
+
if (sessionTitle === undefined)
|
|
626
|
+
return;
|
|
627
|
+
try {
|
|
628
|
+
if (sessionTitle.get(agent.session) !== undefined)
|
|
629
|
+
return;
|
|
630
|
+
sessionTitle.rename(agent.session, member.handle);
|
|
631
|
+
}
|
|
632
|
+
catch {
|
|
633
|
+
// The composition may carry no session-title service, or the rename may
|
|
634
|
+
// race its disposal; the Member works identically without the title.
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
validateMemberPreset(agentCtx) {
|
|
638
|
+
const scope = scopeOf(agentCtx);
|
|
639
|
+
const teamMessage = this.ctx.tools.get('team_message', scope);
|
|
640
|
+
if (teamMessage?.[AGENT_TEAM_PRESET_MARKER] !== true) {
|
|
641
|
+
throw new Error('selected preset is not team-enabled');
|
|
642
|
+
}
|
|
643
|
+
const available = new Set(this.ctx.tools.schemas(scope).map(tool => tool.name));
|
|
644
|
+
const missing = AGENT_TEAM_TOOL_NAMES.filter(name => !available.has(name));
|
|
645
|
+
if (missing.length > 0)
|
|
646
|
+
throw new Error(`team-enabled preset is missing tools: ${missing.join(', ')}`);
|
|
647
|
+
}
|
|
648
|
+
async initializePrivateMemory(path) {
|
|
649
|
+
await mkdir(join(path, 'notes'), { recursive: true });
|
|
650
|
+
try {
|
|
651
|
+
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' });
|
|
652
|
+
}
|
|
653
|
+
catch (error) {
|
|
654
|
+
if (error.code !== 'EEXIST')
|
|
655
|
+
throw error;
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
async cleanupRemovedMember(member) {
|
|
659
|
+
const results = await Promise.allSettled([
|
|
660
|
+
this.ctx.workspaceRegistry.archiveSession(member.sessionId),
|
|
661
|
+
rm(member.privateMemoryPath, { recursive: true, force: true }),
|
|
662
|
+
]);
|
|
663
|
+
const failures = results.flatMap(result => result.status === 'rejected' ? [result.reason] : []);
|
|
664
|
+
if (failures.length > 0)
|
|
665
|
+
throw new AggregateError(failures, `failed to clean up removed Member '${member.memberId}'`);
|
|
666
|
+
}
|
|
667
|
+
memberStatus(member) {
|
|
668
|
+
if (member.state === 'inactive')
|
|
669
|
+
return Object.freeze({ member, availability: 'inactive', presence: 'unavailable' });
|
|
670
|
+
if (member.state === 'suspended')
|
|
671
|
+
return Object.freeze({ member, availability: 'suspended', presence: 'unavailable' });
|
|
672
|
+
const diagnostic = this.diagnostics.get(member.memberId);
|
|
673
|
+
if (diagnostic !== undefined)
|
|
674
|
+
return Object.freeze({ member, availability: 'unavailable', presence: 'unavailable', diagnostic });
|
|
675
|
+
const handle = this.handles.get(member.memberId);
|
|
676
|
+
if (handle === undefined)
|
|
677
|
+
return Object.freeze({ member, availability: 'unavailable', presence: 'unavailable' });
|
|
678
|
+
const runtimeError = this.runtimeErrors.get(handle.agent.id);
|
|
679
|
+
if (runtimeError !== undefined)
|
|
680
|
+
return Object.freeze({ member, availability: 'active', presence: 'error', diagnostic: runtimeError });
|
|
681
|
+
return Object.freeze({ member, availability: 'active', presence: handle.agent.status === 'running' ? 'working' : 'available' });
|
|
682
|
+
}
|
|
683
|
+
requireWorkspace(workspaceId) {
|
|
684
|
+
const workspace = this.ctx.workspaceRegistry.get(workspaceId);
|
|
685
|
+
if (workspace === undefined)
|
|
686
|
+
throw new Error(`unknown Workspace '${workspaceId}'`);
|
|
687
|
+
return workspace;
|
|
688
|
+
}
|
|
689
|
+
requireLedger() {
|
|
690
|
+
if (this.ledger === undefined || this.domain === undefined)
|
|
691
|
+
throw new Error('agent-team service is not initialized');
|
|
692
|
+
return this.ledger;
|
|
693
|
+
}
|
|
694
|
+
requireAccepting() {
|
|
695
|
+
if (!this.accepting)
|
|
696
|
+
throw new Error('agent-team service is shutting down');
|
|
697
|
+
}
|
|
698
|
+
emitCommitted(receipt) {
|
|
699
|
+
this.ctx.emit('agent-team/committed', { receipt });
|
|
700
|
+
const operation = this.ledger?.getOperation(receipt.operationId);
|
|
701
|
+
if (operation === undefined) {
|
|
702
|
+
this.emitChanged();
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
const ledger = this.requireLedger();
|
|
706
|
+
this.emitChanged(ledger.changeScopesOf(operation));
|
|
707
|
+
for (const memberId of ledger.affectedMembersOf(operation)) {
|
|
708
|
+
const handle = this.handles.get(memberId);
|
|
709
|
+
if (handle !== undefined)
|
|
710
|
+
this.notifyMember(handle.agent);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
/** Wake from durable unread state with bounded facts for direct and state-changing work. */
|
|
714
|
+
notifyMember(agent) {
|
|
715
|
+
const member = this.memberForAgent(agent);
|
|
716
|
+
if (member === undefined || member.state !== 'enabled')
|
|
717
|
+
return;
|
|
718
|
+
const notifications = this.requireLedger().notificationFacts(member.memberId, { workspaceId: member.workspaceId });
|
|
719
|
+
if (notifications.length === 0) {
|
|
720
|
+
this.notifiedInbox.delete(member.memberId);
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
const signature = JSON.stringify(notifications.map(({ item }) => [
|
|
724
|
+
item.thread.threadRef, item.thread.revision, item.unreadCount, item.directCount, item.newestSequence,
|
|
725
|
+
]));
|
|
726
|
+
if (this.notifiedInbox.get(member.memberId) === signature)
|
|
727
|
+
return;
|
|
728
|
+
const existingHint = [...agent.inbox.nextStep, ...agent.inbox.nextTurn].find(message => message.source.kind === 'plugin' && message.source.plugin === '@wowyuarm/dsh-agent-team');
|
|
729
|
+
if (existingHint !== undefined)
|
|
730
|
+
agent.inbox.remove(existingHint.id);
|
|
731
|
+
const hint = createUserMessage({
|
|
732
|
+
content: [{ type: 'text', text: this.notificationText(notifications) }],
|
|
733
|
+
source: { kind: 'plugin', plugin: '@wowyuarm/dsh-agent-team', form: 'notice', summary: 'Team Inbox has unread work.' },
|
|
734
|
+
});
|
|
735
|
+
this.notifiedInbox.set(member.memberId, signature);
|
|
736
|
+
try {
|
|
737
|
+
agent.steer(hint);
|
|
738
|
+
}
|
|
739
|
+
catch (error) {
|
|
740
|
+
this.clearMemberNotificationState(member.memberId);
|
|
741
|
+
throw error;
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
notificationText(notifications) {
|
|
745
|
+
const maxCharacters = 32 * 1024;
|
|
746
|
+
const sections = ['Team Inbox has unread work.'];
|
|
747
|
+
let characterCount = sections[0].length;
|
|
748
|
+
let detailedFactCount = 0;
|
|
749
|
+
let omitted = notifications.length > 8;
|
|
750
|
+
const append = (section) => {
|
|
751
|
+
if (characterCount + section.length + 2 > maxCharacters) {
|
|
752
|
+
omitted = true;
|
|
753
|
+
return false;
|
|
754
|
+
}
|
|
755
|
+
sections.push(section);
|
|
756
|
+
characterCount += section.length + 2;
|
|
757
|
+
return true;
|
|
758
|
+
};
|
|
759
|
+
for (const { item, facts } of notifications.slice(0, 8)) {
|
|
760
|
+
for (const { fact, direct } of facts) {
|
|
761
|
+
if (detailedFactCount >= 20) {
|
|
762
|
+
omitted = true;
|
|
763
|
+
break;
|
|
764
|
+
}
|
|
765
|
+
if (direct && fact.kind === 'message') {
|
|
766
|
+
const sender = fact.message.sender === AGENT_TEAM_HUMAN_MEMBER_ID
|
|
767
|
+
? 'human' : this.requireLedger().getMember(fact.message.sender)?.handle ?? fact.message.sender;
|
|
768
|
+
const detail = ['Direct Team mention', `From: ${sender}`, `Channel: ${item.channelRef}`,
|
|
769
|
+
`Task: ${item.task.taskRef}`, `Thread: ${item.thread.threadRef}`, `Message ref: ${fact.message.messageRef}`,
|
|
770
|
+
`Revision: ${item.thread.revision}`, `Message: ${this.boundedNotificationBody(fact.message.body)}`].join('\n');
|
|
771
|
+
if (append(detail))
|
|
772
|
+
detailedFactCount += 1;
|
|
773
|
+
}
|
|
774
|
+
else if (fact.kind === 'activity') {
|
|
775
|
+
const detail = `${this.activityNotification(fact.activity)}\nThread: ${item.thread.threadRef}\nRevision: ${item.thread.revision}`;
|
|
776
|
+
if (append(detail))
|
|
777
|
+
detailedFactCount += 1;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
const ordinaryCount = facts.filter(entry => entry.fact.kind === 'message' && !entry.direct).length;
|
|
781
|
+
if (ordinaryCount > 0)
|
|
782
|
+
append(`Task ${item.task.taskRef}: ${ordinaryCount} unread update${ordinaryCount === 1 ? '' : 's'}; revision ${item.thread.revision}.`);
|
|
783
|
+
}
|
|
784
|
+
if (omitted)
|
|
785
|
+
sections.push('More unread work remains in team_inbox; the automatic context is bounded.');
|
|
786
|
+
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.');
|
|
787
|
+
return sections.join('\n\n');
|
|
788
|
+
}
|
|
789
|
+
boundedNotificationBody(body) {
|
|
790
|
+
const limit = 8 * 1024;
|
|
791
|
+
return body.length <= limit ? body : `${body.slice(0, limit)}\n[Message body truncated; use team_thread read for the full Message.]`;
|
|
792
|
+
}
|
|
793
|
+
activityNotification(activity) {
|
|
794
|
+
const actor = activity.actor === AGENT_TEAM_HUMAN_MEMBER_ID
|
|
795
|
+
? 'human' : this.requireLedger().getMember(activity.actor)?.handle ?? activity.actor;
|
|
796
|
+
if (activity.kind === 'claim' || activity.kind === 'done' || activity.kind === 'release') {
|
|
797
|
+
return `Team Task update\n${actor} ${activity.kind} Claim ${activity.claimRef} on Task ${activity.taskRef}.`;
|
|
798
|
+
}
|
|
799
|
+
if (activity.kind === 'claims_released') {
|
|
800
|
+
return `Team Task update\n${actor}'s Claims ${activity.claimRefs.join(', ')} were released on Task ${activity.taskRef}.`;
|
|
801
|
+
}
|
|
802
|
+
const released = 'releasedClaimRefs' in activity && activity.releasedClaimRefs !== undefined && activity.releasedClaimRefs.length > 0
|
|
803
|
+
? ` Released Claims: ${activity.releasedClaimRefs.join(', ')}.` : '';
|
|
804
|
+
return `Team Task update\n${actor} ${activity.kind} Task ${activity.taskRef}.${released}`;
|
|
805
|
+
}
|
|
806
|
+
clearMemberNotificationState(memberId) {
|
|
807
|
+
this.notifiedInbox.delete(memberId);
|
|
808
|
+
}
|
|
809
|
+
/**
|
|
810
|
+
* Wake waiters for one committed or lifecycle change. Undefined broadcasts
|
|
811
|
+
* to everyone; an empty scope list invalidates nobody because no shared
|
|
812
|
+
* projection changed; otherwise global and matching scoped waiters wake.
|
|
813
|
+
*/
|
|
814
|
+
emitChanged(scopes) {
|
|
815
|
+
this.changeVersion += 1;
|
|
816
|
+
for (const waiter of this.changeWaiters) {
|
|
817
|
+
const waiterScope = waiter.scope;
|
|
818
|
+
if (scopes !== undefined && (waiterScope === undefined ? scopes.length === 0 : !scopes.some(scope => sameChangeScope(scope, waiterScope))))
|
|
819
|
+
continue;
|
|
820
|
+
this.changeWaiters.delete(waiter);
|
|
821
|
+
waiter.wake(this.changeVersion);
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
validateChangeScope(scope) {
|
|
825
|
+
if (scope === undefined)
|
|
826
|
+
return undefined;
|
|
827
|
+
const ref = scope.kind === 'workspace' ? scope.workspaceId : scope.kind === 'channel' ? scope.channelRef : scope.threadRef;
|
|
828
|
+
if (typeof ref !== 'string' || ref.length === 0)
|
|
829
|
+
throw new Error(`change scope of kind '${scope.kind}' requires a non-empty ref`);
|
|
830
|
+
return scope;
|
|
831
|
+
}
|
|
832
|
+
enqueueLifecycle(operation) {
|
|
833
|
+
this.requireAccepting();
|
|
834
|
+
const result = this.lifecycleTail.then(operation);
|
|
835
|
+
this.lifecycleTail = result.then(() => { }, () => { });
|
|
836
|
+
return result;
|
|
837
|
+
}
|
|
838
|
+
};
|
|
839
|
+
})();
|
|
840
|
+
/** Host owner of the single Agent Team in one dshHome. */
|
|
841
|
+
export default AgentTeam;
|