Skip to content

Commit ea053ba

Browse files
author
evolver-publish
committed
Release Evolver v2 beta v2.0.0-beta.2 from ab9720a003eb
1 parent 9145099 commit ea053ba

195 files changed

Lines changed: 10134 additions & 553 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,18 @@ monorepo(pnpm workspaces):
88
- `@evomap/evolver-hub-conformance` — test-only HubCapability contract suite,public/private adapter 共用。
99
- `evolver-proxy` `evolver-mcp` `evolver-cli` `evolver-runtime-adapters` `evolver-webui`
1010

11+
Runtime onboarding: [Antigravity MCP, ingest, and trajectory export](docs/antigravity.md).
12+
1113
企业私有化不维护第二套 v2 产品线;`evolver-v2-enterprise-dev` 只持有 private protocol + private Hub adapter,
1214
并通过 `@evomap/evolver-hub-conformance` 证明它仍实现同一份 core contract。
1315

1416
```bash
15-
pnpm install && pnpm lint && pnpm typecheck && pnpm test && pnpm build
17+
corepack pnpm install
18+
corepack pnpm verify
1619
```
1720

21+
开发中的快速反馈可使用 `corepack pnpm verify:changed`;它不能替代提交前的完整 gate。详见 [本地开发验证](docs/development.md)
22+
1823
## ATP manual commands
1924
v2 restores the v1 consumer-side ATP loop without putting economic wire details into core. Authenticate once,
2025
then place/list/verify orders through the public adapter:

package.json

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@evomap/evolver",
3-
"version": "2.0.0-beta.1",
3+
"version": "2.0.0-beta.2",
44
"private": false,
55
"type": "module",
66
"description": "Evolver v2 beta public distribution package.",
@@ -20,19 +20,20 @@
2020
"bin/",
2121
"index.js",
2222
"README.md",
23+
"release-provenance.json",
2324
"package.json"
2425
],
2526
"engines": {
2627
"node": ">=22"
2728
},
2829
"dependencies": {
29-
"@evomap/evolver-cli": "2.0.0-beta.1",
30-
"@evomap/evolver-mcp": "2.0.0-beta.1",
31-
"@evomap/evolver-proxy": "2.0.0-beta.1",
32-
"@evomap/evolver-core": "2.0.0-beta.1",
33-
"@evomap/evolver-adapter-public": "2.0.0-beta.1",
34-
"@evomap/evolver-runtime-adapters": "2.0.0-beta.1",
35-
"@evomap/evolver-webui": "2.0.0-beta.1"
30+
"@evomap/evolver-cli": "2.0.0-beta.2",
31+
"@evomap/evolver-mcp": "2.0.0-beta.2",
32+
"@evomap/evolver-proxy": "2.0.0-beta.2",
33+
"@evomap/evolver-core": "2.0.0-beta.2",
34+
"@evomap/evolver-adapter-public": "2.0.0-beta.2",
35+
"@evomap/evolver-runtime-adapters": "2.0.0-beta.2",
36+
"@evomap/evolver-webui": "2.0.0-beta.2"
3637
},
3738
"publishConfig": {
3839
"access": "public",
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { type hub } from '@evomap/evolver-core';
2+
export declare const PUBLIC_TASK_DISCOVERY_MAX_CANDIDATES: number;
3+
export declare function publicAgentSearchQuery(request: hub.AgentSearchRequest): Record<string, string | number | undefined>;
4+
export declare function unsupportedPublicAvailability(availability: hub.AgentAvailability | undefined): hub.AgentDirectoryResult<never> | undefined;
5+
export declare function unsupportedPublicSort(sort: hub.AgentDirectorySort | undefined): hub.AgentDirectoryResult<never> | undefined;
6+
export declare function publicTaskDiscoveryQuery(request: hub.AgentTaskDiscoveryRequest): Record<string, string | number | undefined>;
7+
export declare function parsePublicAgentPage(body: unknown): hub.AgentDirectoryResult<hub.AgentDirectoryPage>;
8+
export declare function paginatePublicAgentPage(page: hub.AgentDirectoryResult<hub.AgentDirectoryPage>, request: hub.AgentSearchRequest, maxOffset?: number): hub.AgentDirectoryResult<hub.AgentDirectoryPage>;
9+
export declare function mergePublicAgentPages(pages: readonly hub.AgentDirectoryResult<hub.AgentDirectoryPage>[]): hub.AgentDirectoryResult<hub.AgentDirectoryPage>;
10+
export declare function parsePublicAgentProfile(body: unknown): hub.AgentDirectoryResult<hub.AgentProfile | null>;
11+
export declare function agentDirectoryFailure(error: unknown): hub.AgentDirectoryResult<never>;
12+
export declare function withDirectoryTimeout<T>(operation: Promise<T>, timeoutMs: number): Promise<T>;
Lines changed: 293 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,293 @@
1+
import { hub as hubNs } from '@evomap/evolver-core';
2+
import { AuthError, HubClientError, HubUnreachableError } from './hubFetch.js';
3+
const MAX_TEXT_LENGTH = 500;
4+
const MAX_CAPABILITY_COUNT = 50;
5+
export const PUBLIC_TASK_DISCOVERY_MAX_CANDIDATES = hubNs.AGENT_DIRECTORY_MAX_LIMIT * 2;
6+
export function publicAgentSearchQuery(request) {
7+
const normalized = hubNs.normalizeAgentSearchRequest(request);
8+
return {
9+
...(normalized.query ? { q: normalized.query } : {}),
10+
...(normalized.signals && normalized.signals.length > 0 ? { signals: normalized.signals.join(',') } : {}),
11+
limit: hubNs.AGENT_DIRECTORY_MAX_LIMIT,
12+
online_only: normalized.availability === 'offline' ? 'false' : 'true',
13+
};
14+
}
15+
export function unsupportedPublicAvailability(availability) {
16+
if (availability === 'busy' || availability === 'offline' || availability === 'unknown') {
17+
return hubNs.capabilityUnavailable(`public_hub_availability_${availability}_not_supported`);
18+
}
19+
return undefined;
20+
}
21+
export function unsupportedPublicSort(sort) {
22+
if (sort === 'recent')
23+
return hubNs.capabilityUnavailable('public_hub_sort_recent_not_supported');
24+
return undefined;
25+
}
26+
export function publicTaskDiscoveryQuery(request) {
27+
return publicAgentSearchQuery(hubNs.normalizeAgentTaskDiscoveryRequest(request));
28+
}
29+
export function parsePublicAgentPage(body) {
30+
const record = asRecord(body);
31+
const payload = asRecord(record?.['payload']) ?? record;
32+
const rawItems = Array.isArray(payload?.['results'])
33+
? payload['results']
34+
: Array.isArray(payload?.['agents'])
35+
? payload['agents']
36+
: Array.isArray(body)
37+
? body
38+
: undefined;
39+
if (!rawItems)
40+
return invalidResponse('agent_directory_results_missing');
41+
const items = [];
42+
for (const raw of rawItems) {
43+
const parsed = parseAgentEntry(raw);
44+
if (!parsed)
45+
return invalidResponse('agent_directory_entry_invalid');
46+
items.push(parsed);
47+
}
48+
const nextCursor = optionalString(payload?.['next_cursor'] ?? payload?.['nextCursor'], 256);
49+
const hasMoreValue = payload?.['has_more'] ?? payload?.['hasMore'];
50+
if (hasMoreValue !== undefined && typeof hasMoreValue !== 'boolean')
51+
return invalidResponse('agent_directory_has_more_invalid');
52+
return { ok: true, value: { items, ...(nextCursor ? { nextCursor } : {}), hasMore: typeof hasMoreValue === 'boolean' ? hasMoreValue : Boolean(nextCursor) } };
53+
}
54+
export function paginatePublicAgentPage(page, request, maxOffset = hubNs.AGENT_DIRECTORY_MAX_LIMIT) {
55+
if (!page.ok)
56+
return page;
57+
const normalized = hubNs.normalizeAgentSearchRequest(request);
58+
const offset = cursorOffset(normalized.cursor, maxOffset);
59+
const filtered = normalized.availability
60+
? page.value.items.filter((item) => item.availability === normalized.availability)
61+
: page.value.items;
62+
const items = [...filtered].sort(agentComparator(normalized.sort, normalized.order));
63+
const slice = items.slice(offset, offset + normalized.limit);
64+
const nextOffset = offset + slice.length;
65+
return {
66+
ok: true,
67+
value: {
68+
items: slice,
69+
...(nextOffset < items.length ? { nextCursor: `offset:${nextOffset}` } : {}),
70+
hasMore: nextOffset < items.length,
71+
},
72+
};
73+
}
74+
export function mergePublicAgentPages(pages) {
75+
const failed = pages.find((page) => !page.ok);
76+
if (failed && !failed.ok)
77+
return failed;
78+
const items = new Map();
79+
for (const page of pages) {
80+
if (!page.ok)
81+
continue;
82+
for (const item of page.value.items) {
83+
const previous = items.get(item.agentId);
84+
if (!previous || (item.score ?? -1) > (previous.score ?? -1))
85+
items.set(item.agentId, item);
86+
}
87+
}
88+
return { ok: true, value: { items: [...items.values()], hasMore: false } };
89+
}
90+
export function parsePublicAgentProfile(body) {
91+
if (body === null)
92+
return { ok: true, value: null };
93+
const record = asRecord(body);
94+
const payload = asRecord(record?.['payload']) ?? record;
95+
const profile = asRecord(payload?.['profile']) ?? payload;
96+
if (!profile)
97+
return invalidResponse('agent_profile_invalid');
98+
const entry = parseAgentEntry(profile);
99+
if (!entry)
100+
return invalidResponse('agent_profile_invalid');
101+
const taskStatsCompletedCount = completedTaskCountFromTaskStats(profile['task_stats']);
102+
if (taskStatsCompletedCount === null)
103+
return invalidResponse('agent_profile_invalid');
104+
const { score: _score, ...safeProfile } = entry;
105+
return {
106+
ok: true,
107+
value: {
108+
...safeProfile,
109+
...(safeProfile.completedTaskCount === undefined && taskStatsCompletedCount !== undefined
110+
? { completedTaskCount: taskStatsCompletedCount }
111+
: {}),
112+
},
113+
};
114+
}
115+
export function agentDirectoryFailure(error) {
116+
if (error instanceof hubNs.AgentDirectoryInputError)
117+
return { ok: false, error: { code: 'invalid_request', retryable: false, message: error.message } };
118+
if (error instanceof DirectoryTimeoutError)
119+
return { ok: false, error: { code: 'timeout', retryable: true, message: 'agent_directory_timeout' } };
120+
if (error instanceof AuthError)
121+
return { ok: false, error: { code: 'permission_denied', retryable: false, message: 'agent_directory_permission_denied' } };
122+
if (error instanceof HubClientError && (error.status === 404 || error.status === 405 || error.status === 501))
123+
return hubNs.capabilityUnavailable();
124+
if (error instanceof HubUnreachableError || isNetworkError(error))
125+
return { ok: false, error: { code: 'hub_unavailable', retryable: true, message: 'agent_directory_hub_unavailable' } };
126+
if (error instanceof HubClientError)
127+
return { ok: false, error: { code: 'invalid_request', retryable: false, message: `agent_directory_hub_${error.status}` } };
128+
return { ok: false, error: { code: 'hub_unavailable', retryable: true, message: 'agent_directory_request_failed' } };
129+
}
130+
export async function withDirectoryTimeout(operation, timeoutMs) {
131+
let timer;
132+
try {
133+
return await Promise.race([
134+
operation,
135+
new Promise((_resolve, reject) => {
136+
timer = setTimeout(() => reject(new DirectoryTimeoutError()), timeoutMs);
137+
}),
138+
]);
139+
}
140+
finally {
141+
if (timer)
142+
clearTimeout(timer);
143+
}
144+
}
145+
class DirectoryTimeoutError extends Error {
146+
}
147+
function parseAgentEntry(value) {
148+
const record = asRecord(value);
149+
if (!record)
150+
return undefined;
151+
const agentId = optionalString(record['agent_id'] ?? record['agentId'] ?? record['node_id'] ?? record['nodeId'], 128);
152+
if (!agentId)
153+
return undefined;
154+
const capabilitiesValue = record['capabilities'];
155+
const capabilities = stringList(capabilitiesValue === undefined || capabilitiesValue === null ? record['signals'] : capabilitiesValue);
156+
const domains = stringArray(record['domains']);
157+
if (capabilities === null || domains === null)
158+
return undefined;
159+
const availability = availabilityValue(record['availability'], record['online']);
160+
const score = optionalBoundedNumber(record['score'], 0, 1);
161+
const reputation = optionalBoundedNumber(record['reputation'], 0, 1_000_000);
162+
const completedTaskCount = optionalInteger(record['completed_tasks'] ?? record['completedTaskCount'], 0);
163+
const lastSeenAt = optionalTimestamp(record['last_seen_at'] ?? record['lastSeenAt']);
164+
if (availability === null || score === null || reputation === null || completedTaskCount === null || lastSeenAt === null)
165+
return undefined;
166+
return {
167+
agentId,
168+
...(optionalString(record['display_name'] ?? record['displayName'] ?? record['name'] ?? record['alias'], 120) ? { displayName: optionalString(record['display_name'] ?? record['displayName'] ?? record['name'] ?? record['alias'], 120) } : {}),
169+
...(optionalString(record['summary'] ?? record['description'], MAX_TEXT_LENGTH) ? { summary: optionalString(record['summary'] ?? record['description'], MAX_TEXT_LENGTH) } : {}),
170+
...(capabilities !== undefined ? { capabilities } : {}),
171+
...(domains !== undefined ? { domains } : {}),
172+
...(score !== undefined ? { score } : {}),
173+
...(reputation !== undefined ? { reputation } : {}),
174+
...(completedTaskCount !== undefined ? { completedTaskCount } : {}),
175+
...(availability ? { availability } : {}),
176+
...(lastSeenAt !== undefined ? { lastSeenAt } : {}),
177+
};
178+
}
179+
function asRecord(value) {
180+
return value && typeof value === 'object' && !Array.isArray(value) ? value : undefined;
181+
}
182+
function hasOwn(record, key) {
183+
return Object.prototype.hasOwnProperty.call(record, key);
184+
}
185+
function optionalString(value, maxLength) {
186+
if (value === undefined || value === null)
187+
return undefined;
188+
if (typeof value !== 'string')
189+
return undefined;
190+
const text = value.trim();
191+
return text && text.length <= maxLength ? text : undefined;
192+
}
193+
function stringArray(value) {
194+
if (value === undefined || value === null)
195+
return undefined;
196+
if (!Array.isArray(value) || value.length > MAX_CAPABILITY_COUNT)
197+
return null;
198+
const output = [];
199+
for (const item of value) {
200+
const text = optionalString(item, 64);
201+
if (!text)
202+
return null;
203+
if (!output.includes(text))
204+
output.push(text);
205+
}
206+
return output;
207+
}
208+
function stringList(value) {
209+
if (typeof value === 'string')
210+
return stringArray(value.split(',').map((item) => item.trim()).filter(Boolean));
211+
return stringArray(value);
212+
}
213+
function completedTaskCountFromTaskStats(value) {
214+
if (value === undefined)
215+
return undefined;
216+
const taskStats = asRecord(value);
217+
if (!taskStats)
218+
return null;
219+
if (!hasOwn(taskStats, 'completed'))
220+
return undefined;
221+
const completed = taskStats['completed'];
222+
if (!Number.isInteger(completed) || Number(completed) < 0)
223+
return null;
224+
return Number(completed);
225+
}
226+
function availabilityValue(value, online) {
227+
if (value === 'online' || value === 'busy' || value === 'offline' || value === 'unknown')
228+
return value;
229+
if (value !== undefined)
230+
return null;
231+
if (online === true)
232+
return 'online';
233+
if (online === false)
234+
return 'offline';
235+
if (online !== undefined)
236+
return null;
237+
return undefined;
238+
}
239+
function optionalBoundedNumber(value, min, max) {
240+
if (value === undefined || value === null)
241+
return undefined;
242+
if (typeof value !== 'number' || !Number.isFinite(value) || value < min || value > max)
243+
return null;
244+
return value;
245+
}
246+
function optionalInteger(value, min) {
247+
if (value === undefined || value === null)
248+
return undefined;
249+
if (!Number.isInteger(value) || Number(value) < min)
250+
return null;
251+
return Number(value);
252+
}
253+
function optionalTimestamp(value) {
254+
if (value === undefined || value === null)
255+
return undefined;
256+
if (typeof value === 'number' && Number.isFinite(value) && value >= 0)
257+
return value;
258+
if (typeof value === 'string' && value.length <= 64) {
259+
const parsed = Date.parse(value);
260+
return Number.isFinite(parsed) ? parsed : null;
261+
}
262+
return null;
263+
}
264+
function invalidResponse(message) {
265+
return { ok: false, error: { code: 'invalid_response', retryable: false, message } };
266+
}
267+
function cursorOffset(cursor, maxOffset) {
268+
if (!cursor)
269+
return 0;
270+
const match = /^offset:(\d+)$/.exec(cursor);
271+
const offset = match ? Number(match[1]) : NaN;
272+
if (!Number.isInteger(offset) || offset < 0 || offset > maxOffset) {
273+
throw new hubNs.AgentDirectoryInputError('cursor_invalid');
274+
}
275+
return offset;
276+
}
277+
function agentComparator(sort, order) {
278+
const direction = order === 'asc' ? 1 : -1;
279+
return (left, right) => direction * (sortValue(left, sort) - sortValue(right, sort)) || left.agentId.localeCompare(right.agentId);
280+
}
281+
function sortValue(entry, sort) {
282+
if (sort === 'reputation')
283+
return entry.reputation ?? -1;
284+
if (sort === 'recent')
285+
return entry.lastSeenAt ?? -1;
286+
if (sort === 'availability')
287+
return entry.availability === 'online' ? 3 : entry.availability === 'busy' ? 2 : entry.availability === 'unknown' ? 1 : 0;
288+
return entry.score ?? -1;
289+
}
290+
function isNetworkError(error) {
291+
const record = asRecord(error);
292+
return record?.['name'] === 'AbortError' || record?.['name'] === 'TimeoutError' || record?.['code'] === 'HUB_UNREACHABLE';
293+
}

packages/evolver-adapter-public/dist/antiAbuseTelemetry.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export interface AntiAbuseTelemetryOptions {
1717
evolverVersion?: string;
1818
taskMeta?: Record<string, unknown>;
1919
workspaceId?: string;
20+
workspaceIdResolver?: () => string | null;
2021
}
2122
export interface IntegrityHashes {
2223
package_json_hash: string | null;

packages/evolver-adapter-public/dist/antiAbuseTelemetry.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,10 +103,10 @@ export function buildHeartbeatAntiAbuseTelemetry(opts = {}) {
103103
const saltId = opts.saltId ?? env['EVOLVER_ANTI_ABUSE_SALT_ID'] ?? (salt ? 'env' : null);
104104
const pseudonymStatus = salt ? 'salt_configured' : 'salt_missing';
105105
const devicePseudonym = hmacPseudonym(fp.device, { salt, purpose: 'device' });
106-
const workspacePseudonym = hmacPseudonym(opts.workspaceId ?? process.cwd(), { salt, purpose: 'workspace' });
106+
const workspacePseudonym = salt ? hmacPseudonym(resolveTelemetryWorkspaceId(opts), { salt, purpose: 'workspace' }) : null;
107107
const unavailableFields = [
108108
...(devicePseudonym ? [] : [unavailable('device_pseudonym', 'anti_abuse_salt_missing', 'signed_policy_or_env')]),
109-
...(workspacePseudonym ? [] : [unavailable('workspace_pseudonym', 'anti_abuse_salt_missing', 'signed_policy_or_env')]),
109+
...(workspacePseudonym ? [] : [unavailable('workspace_pseudonym', salt ? 'workspace_id_unavailable' : 'anti_abuse_salt_missing', salt ? 'workspace_keychain_or_filesystem' : 'signed_policy_or_env')]),
110110
unavailable('client_ip', 'server_observed_required', 'hub_edge'),
111111
unavailable('asn', 'server_observed_required', 'hub_edge'),
112112
unavailable('proxy_vpn_tor_datacenter_class', 'server_observed_required', 'hub_edge'),
@@ -213,6 +213,13 @@ function nowIso(now) {
213213
return now;
214214
return new Date().toISOString();
215215
}
216+
function resolveTelemetryWorkspaceId(opts) {
217+
if (opts.workspaceId)
218+
return opts.workspaceId;
219+
if (opts.workspaceIdResolver)
220+
return opts.workspaceIdResolver();
221+
return process.cwd();
222+
}
216223
function boolFromEnv(value) {
217224
const v = String(value ?? '').trim().toLowerCase();
218225
return v === '1' || v === 'true' || v === 'yes' || v === 'on';

0 commit comments

Comments
 (0)