@evomap/evolver-adapter-public 2.0.0-beta.8 → 2.0.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/dist/antiAbuseTelemetry.js +2 -1
- package/dist/auth/credentialStore.d.ts +87 -3
- package/dist/auth/credentialStore.js +1065 -10
- package/dist/auth/oauthDeviceToken.d.ts +9 -6
- package/dist/auth/oauthDeviceToken.js +71 -18
- package/dist/auth/oauthHttpTransport.d.ts +4 -0
- package/dist/auth/oauthHttpTransport.js +66 -15
- package/dist/auth/windowsPowerShell.d.ts +3 -0
- package/dist/auth/windowsPowerShell.js +91 -0
- package/dist/hubCapability.d.ts +16 -4
- package/dist/hubCapability.js +322 -45
- package/dist/hubFetch.d.ts +44 -11
- package/dist/hubFetch.js +329 -76
- package/dist/hubReuse.d.ts +40 -0
- package/dist/hubReuse.js +303 -32
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/learningPacketFeedback.d.ts +68 -0
- package/dist/learningPacketFeedback.js +104 -0
- package/dist/learningPacketSink.d.ts +40 -0
- package/dist/learningPacketSink.js +153 -0
- package/dist/wireMap.d.ts +3 -1
- package/dist/wireMap.js +29 -3
- package/package.json +6 -3
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { hub, trace } from '@evomap/evolver-core';
|
|
2
|
+
import { type FetchLike } from './hubFetch.js';
|
|
3
|
+
/** Hub traceEvents array cap (createLearningPacketSchema). Extra events are dropped, noted in metadata. */
|
|
4
|
+
export declare const HUB_TRACE_EVENTS_MAX = 100;
|
|
5
|
+
export interface HubLearningPacketSinkOptions {
|
|
6
|
+
baseUrl: string;
|
|
7
|
+
auth: hub.AuthProvider;
|
|
8
|
+
fetchFn: FetchLike;
|
|
9
|
+
/** Optional node identity recorded on the packet (hub nodeId column). */
|
|
10
|
+
nodeId?: () => string | undefined;
|
|
11
|
+
}
|
|
12
|
+
/** Deterministic content hash over the draft body (hub contentHash column, dedup aid). */
|
|
13
|
+
export declare function learningPacketContentHash(draft: trace.LearningPacketDraft): string;
|
|
14
|
+
/**
|
|
15
|
+
* Auth headers for the strict learning-packets routes (requireAuth reads Authorization only).
|
|
16
|
+
* A legacy body credential (bodyFields.node_secret) is promoted to Bearer — the strict schemas
|
|
17
|
+
* reject extra body fields, so credentials must never ride in the body here.
|
|
18
|
+
*/
|
|
19
|
+
export declare function learningOpsAuthHeaders(auth: hub.AuthProvider, method: string, path: string): Promise<Record<string, string>>;
|
|
20
|
+
/** Map a core draft to the hub createLearningPacket body. Exported for tests and for the private adapter to reuse. */
|
|
21
|
+
export declare function learningPacketWireBody(draft: trace.LearningPacketDraft, nodeId?: string): Record<string, unknown>;
|
|
22
|
+
/**
|
|
23
|
+
* LearningPacketSink implementation against the public hub Learning Ops ingest API. Best-effort by
|
|
24
|
+
* contract: every failure returns { accepted: false, reason } (never throws) — the runtime treats packet
|
|
25
|
+
* delivery as observability, so a hub outage must never affect a task verdict. A 409 duplicate_source is
|
|
26
|
+
* reported as accepted (the packet is already there; the idempotency key did its job).
|
|
27
|
+
*/
|
|
28
|
+
export declare class HubLearningPacketSink implements trace.LearningPacketSink {
|
|
29
|
+
private readonly opts;
|
|
30
|
+
constructor(opts: HubLearningPacketSinkOptions);
|
|
31
|
+
submit(draft: trace.LearningPacketDraft): Promise<trace.LearningPacketSubmitResult>;
|
|
32
|
+
}
|
|
33
|
+
/** Fan-out: always deliver to `primary` (local file record), then best-effort to `secondary` (hub upload).
|
|
34
|
+
* The composite result reflects the PRIMARY sink — the local record is the durability guarantee. */
|
|
35
|
+
export declare class TeeLearningPacketSink implements trace.LearningPacketSink {
|
|
36
|
+
private readonly primary;
|
|
37
|
+
private readonly secondary;
|
|
38
|
+
constructor(primary: trace.LearningPacketSink, secondary: trace.LearningPacketSink);
|
|
39
|
+
submit(draft: trace.LearningPacketDraft): Promise<trace.LearningPacketSubmitResult>;
|
|
40
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
// Learning Ops packet upload (slice 3) — the adapter half of core's LearningPacketSink port.
|
|
2
|
+
// Maps a local LearningPacketDraft (learning_packet.v0, built by evolver-core/trace) onto the hub's
|
|
3
|
+
// `POST /api/learning-packets` ingest contract (strict zod schema, requireAuth Bearer token).
|
|
4
|
+
//
|
|
5
|
+
// Deliberately NOT built on HubFetch: that helper injects sender_id/credential fields into every POST body
|
|
6
|
+
// (the /a2a envelope convention), which the strict learning-packets schema rejects. This sink authenticates
|
|
7
|
+
// via the injected AuthProvider (Authorization header only) and sends exactly the schema's fields.
|
|
8
|
+
import { createHash } from 'node:crypto';
|
|
9
|
+
import { assertHubUrlSecure, isHubUnreachableError } from './hubFetch.js';
|
|
10
|
+
/** Hub traceEvents array cap (createLearningPacketSchema). Extra events are dropped, noted in metadata. */
|
|
11
|
+
export const HUB_TRACE_EVENTS_MAX = 100;
|
|
12
|
+
/** Hub failureCategory is a closed enum; runtime failureKind is looser. Only the sure mapping is direct. */
|
|
13
|
+
function failureCategoryFor(failureKind) {
|
|
14
|
+
if (failureKind === null)
|
|
15
|
+
return undefined;
|
|
16
|
+
if (failureKind === 'permission_denied')
|
|
17
|
+
return 'permission_error';
|
|
18
|
+
if (failureKind === 'timeout' || failureKind === 'non_zero_exit' || failureKind === 'invalid_output')
|
|
19
|
+
return 'tool_error';
|
|
20
|
+
return 'other';
|
|
21
|
+
}
|
|
22
|
+
function outcomeStatusFor(status) {
|
|
23
|
+
if (status === 'success')
|
|
24
|
+
return 'succeeded';
|
|
25
|
+
if (status === 'failed')
|
|
26
|
+
return 'failed';
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
/** Deterministic content hash over the draft body (hub contentHash column, dedup aid). */
|
|
30
|
+
export function learningPacketContentHash(draft) {
|
|
31
|
+
return `sha256:${createHash('sha256').update(JSON.stringify(draft)).digest('hex')}`;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Auth headers for the strict learning-packets routes (requireAuth reads Authorization only).
|
|
35
|
+
* A legacy body credential (bodyFields.node_secret) is promoted to Bearer — the strict schemas
|
|
36
|
+
* reject extra body fields, so credentials must never ride in the body here.
|
|
37
|
+
*/
|
|
38
|
+
export async function learningOpsAuthHeaders(auth, method, path) {
|
|
39
|
+
const signed = await auth.authenticate({ method, path });
|
|
40
|
+
const headers = { 'content-type': 'application/json', ...(signed.headers ?? {}) };
|
|
41
|
+
const bodySecret = signed.bodyFields?.['node_secret'];
|
|
42
|
+
if (headers['authorization'] === undefined && bodySecret !== undefined)
|
|
43
|
+
headers['authorization'] = `Bearer ${String(bodySecret)}`;
|
|
44
|
+
return headers;
|
|
45
|
+
}
|
|
46
|
+
/** Map a core draft to the hub createLearningPacket body. Exported for tests and for the private adapter to reuse. */
|
|
47
|
+
export function learningPacketWireBody(draft, nodeId) {
|
|
48
|
+
const truncated = draft.trajectory.length > HUB_TRACE_EVENTS_MAX;
|
|
49
|
+
const events = draft.trajectory.slice(0, HUB_TRACE_EVENTS_MAX);
|
|
50
|
+
const outcomeStatus = outcomeStatusFor(draft.evaluation.outcomeStatus);
|
|
51
|
+
const failureCategory = failureCategoryFor(draft.evaluation.failureCategory);
|
|
52
|
+
return {
|
|
53
|
+
schemaVersion: draft.schemaVersion,
|
|
54
|
+
status: 'draft',
|
|
55
|
+
sourceRepo: draft.source.repo,
|
|
56
|
+
sourceRun: draft.source.run,
|
|
57
|
+
sourceType: draft.source.type,
|
|
58
|
+
sourceId: draft.source.id,
|
|
59
|
+
// One packet per run: the run id IS the idempotency key, so a retried submit dedups hub-side (409).
|
|
60
|
+
idempotencyKey: `${draft.source.repo}:${draft.source.run}`,
|
|
61
|
+
contentHash: learningPacketContentHash(draft),
|
|
62
|
+
...(nodeId ? { nodeId } : {}),
|
|
63
|
+
...(outcomeStatus ? { outcomeStatus } : {}),
|
|
64
|
+
// evaluation fill-in (slice 6): a non-placeholder evaluation carries the runtime's external verifier
|
|
65
|
+
// ('automated_test' is in the hub VERIFIERS enum); passed/score details ride inside payload.evaluation.
|
|
66
|
+
...(draft.evaluation.verifier !== null ? { verifier: draft.evaluation.verifier } : {}),
|
|
67
|
+
...(failureCategory ? { failureCategory } : {}),
|
|
68
|
+
...(draft.task.summary !== null ? { summary: draft.task.summary } : {}),
|
|
69
|
+
payload: {
|
|
70
|
+
task: draft.task,
|
|
71
|
+
context: draft.context,
|
|
72
|
+
artifacts: draft.artifacts,
|
|
73
|
+
evaluation: draft.evaluation,
|
|
74
|
+
governance: draft.governance,
|
|
75
|
+
},
|
|
76
|
+
metadata: {
|
|
77
|
+
...(truncated ? { traceEventsTruncated: true, traceEventsTotal: draft.trajectory.length } : {}),
|
|
78
|
+
...(draft.evaluation.failureCategory !== null ? { runtimeFailureKind: draft.evaluation.failureCategory } : {}),
|
|
79
|
+
},
|
|
80
|
+
redactionStatus: draft.governance.redactionStatus,
|
|
81
|
+
consentStatus: draft.governance.consentStatus,
|
|
82
|
+
retentionPolicy: draft.governance.retentionPolicy,
|
|
83
|
+
traceEvents: events.map((e) => ({
|
|
84
|
+
eventId: e.eventId,
|
|
85
|
+
schemaVersion: e.schemaVersion,
|
|
86
|
+
eventType: e.eventType,
|
|
87
|
+
occurredAt: e.occurredAt,
|
|
88
|
+
traceId: e.traceId,
|
|
89
|
+
...(e.sessionId !== undefined ? { sessionId: e.sessionId } : {}),
|
|
90
|
+
...(e.taskId !== undefined ? { taskId: e.taskId } : {}),
|
|
91
|
+
sequence: e.sequence,
|
|
92
|
+
payload: e.payload,
|
|
93
|
+
metadata: e.metadata,
|
|
94
|
+
})),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* LearningPacketSink implementation against the public hub Learning Ops ingest API. Best-effort by
|
|
99
|
+
* contract: every failure returns { accepted: false, reason } (never throws) — the runtime treats packet
|
|
100
|
+
* delivery as observability, so a hub outage must never affect a task verdict. A 409 duplicate_source is
|
|
101
|
+
* reported as accepted (the packet is already there; the idempotency key did its job).
|
|
102
|
+
*/
|
|
103
|
+
export class HubLearningPacketSink {
|
|
104
|
+
opts;
|
|
105
|
+
constructor(opts) {
|
|
106
|
+
this.opts = opts;
|
|
107
|
+
}
|
|
108
|
+
async submit(draft) {
|
|
109
|
+
try {
|
|
110
|
+
const url = `${this.opts.baseUrl}/api/learning-packets`;
|
|
111
|
+
assertHubUrlSecure(url);
|
|
112
|
+
const headers = await learningOpsAuthHeaders(this.opts.auth, 'POST', '/api/learning-packets');
|
|
113
|
+
const res = await this.opts.fetchFn(url, {
|
|
114
|
+
method: 'POST',
|
|
115
|
+
headers,
|
|
116
|
+
redirect: 'manual',
|
|
117
|
+
body: JSON.stringify(learningPacketWireBody(draft, this.opts.nodeId?.())),
|
|
118
|
+
});
|
|
119
|
+
if (res.status === 201) {
|
|
120
|
+
const body = await res.json().catch(() => null);
|
|
121
|
+
const packet = body && typeof body === 'object' ? body.packet : undefined;
|
|
122
|
+
return { accepted: true, ...(typeof packet?.id === 'string' ? { reason: packet.id } : {}) };
|
|
123
|
+
}
|
|
124
|
+
if (res.status === 409)
|
|
125
|
+
return { accepted: true, reason: 'duplicate_source' };
|
|
126
|
+
const text = await res.text().catch(() => '');
|
|
127
|
+
return { accepted: false, reason: `hub ${res.status}${text ? `: ${text.slice(0, 200)}` : ''}` };
|
|
128
|
+
}
|
|
129
|
+
catch (e) {
|
|
130
|
+
if (isHubUnreachableError(e))
|
|
131
|
+
return { accepted: false, reason: 'hub_unreachable' };
|
|
132
|
+
return { accepted: false, reason: e instanceof Error ? e.message : String(e) };
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/** Fan-out: always deliver to `primary` (local file record), then best-effort to `secondary` (hub upload).
|
|
137
|
+
* The composite result reflects the PRIMARY sink — the local record is the durability guarantee. */
|
|
138
|
+
export class TeeLearningPacketSink {
|
|
139
|
+
primary;
|
|
140
|
+
secondary;
|
|
141
|
+
constructor(primary, secondary) {
|
|
142
|
+
this.primary = primary;
|
|
143
|
+
this.secondary = secondary;
|
|
144
|
+
}
|
|
145
|
+
async submit(draft) {
|
|
146
|
+
const primary = await this.primary.submit(draft);
|
|
147
|
+
try {
|
|
148
|
+
await this.secondary.submit(draft);
|
|
149
|
+
}
|
|
150
|
+
catch { /* secondary is best-effort by contract; sinks should not throw, but never let one break the record */ }
|
|
151
|
+
return primary;
|
|
152
|
+
}
|
|
153
|
+
}
|
package/dist/wireMap.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ export declare function inboundToAgentEvent(m: Record<string, unknown>): hub.Age
|
|
|
6
6
|
* payload.signals, #69)。text 不是 fetch 字段(自由文本走 semantic-search 端点, 见 hubCapability.search)。
|
|
7
7
|
*/
|
|
8
8
|
export declare function searchQueryToFetchWire(q: hub.HubQuery): Record<string, unknown>;
|
|
9
|
+
/** Free discovery phase on /a2a/fetch. Keep this separate from the paid/full fetch mapper. */
|
|
10
|
+
export declare function searchQueryToSearchOnlyWire(q: hub.HubQuery): Record<string, unknown>;
|
|
9
11
|
/** core AgentEvent(出站) → 公版 outbound 消息(id+type 必填). */
|
|
10
12
|
export declare function agentEventToOutbound(e: hub.AgentEvent): Record<string, unknown>;
|
|
11
13
|
/** Retry policy for a non-2xx hub status, shared by every money-touching caller (anti-drift, #177). */
|
|
@@ -25,4 +27,4 @@ export type AtpRetryClass = 'permanent' | 'cooldown' | 'recoverable';
|
|
|
25
27
|
*/
|
|
26
28
|
export declare function atpRetryClass(status: number): AtpRetryClass;
|
|
27
29
|
/** /a2a/publish 响应 → PublishReceipt. 200=accepted; 402/4xx=rejected 终态. */
|
|
28
|
-
export declare function publishRespToReceipt(status: number, body: Record<string, unknown
|
|
30
|
+
export declare function publishRespToReceipt(status: number, body: Record<string, unknown>, retryAfterMs?: number): hub.PublishReceipt;
|
package/dist/wireMap.js
CHANGED
|
@@ -24,10 +24,16 @@ export function searchQueryToFetchWire(q) {
|
|
|
24
24
|
out['category'] = q.category;
|
|
25
25
|
if (q.gene)
|
|
26
26
|
out['gene'] = q.gene;
|
|
27
|
+
if (q.domain)
|
|
28
|
+
out['domain'] = q.domain;
|
|
27
29
|
if (q.limit !== undefined)
|
|
28
30
|
out['limit'] = q.limit;
|
|
29
31
|
return out;
|
|
30
32
|
}
|
|
33
|
+
/** Free discovery phase on /a2a/fetch. Keep this separate from the paid/full fetch mapper. */
|
|
34
|
+
export function searchQueryToSearchOnlyWire(q) {
|
|
35
|
+
return { ...searchQueryToFetchWire(q), search_only: true };
|
|
36
|
+
}
|
|
31
37
|
/** core AgentEvent(出站) → 公版 outbound 消息(id+type 必填). */
|
|
32
38
|
export function agentEventToOutbound(e) {
|
|
33
39
|
return {
|
|
@@ -59,14 +65,20 @@ export function atpRetryClass(status) {
|
|
|
59
65
|
return 'recoverable';
|
|
60
66
|
}
|
|
61
67
|
/** /a2a/publish 响应 → PublishReceipt. 200=accepted; 402/4xx=rejected 终态. */
|
|
62
|
-
export function publishRespToReceipt(status, body) {
|
|
68
|
+
export function publishRespToReceipt(status, body, retryAfterMs) {
|
|
63
69
|
const payload = body['payload'] ?? body;
|
|
64
70
|
const assetIds = payload['asset_ids'];
|
|
65
|
-
const
|
|
71
|
+
const targetAssetId = payload['target_asset_id']
|
|
72
|
+
?? body['target_asset_id'];
|
|
73
|
+
const assetId = (status === 409 ? targetAssetId : undefined)
|
|
74
|
+
?? payload['asset_id']
|
|
75
|
+
?? body['asset_id']
|
|
76
|
+
?? assetIds?.[0]
|
|
77
|
+
?? targetAssetId;
|
|
66
78
|
const bundleId = payload['bundle_id'];
|
|
67
79
|
if (status >= 200 && status < 300) {
|
|
68
80
|
const decision = String(payload['decision'] ?? payload['status'] ?? 'accepted');
|
|
69
|
-
const accepted = decision === 'accepted' || decision === 'approved' || decision === 'ok';
|
|
81
|
+
const accepted = decision === 'accept' || decision === 'accepted' || decision === 'approved' || decision === 'ok';
|
|
70
82
|
return {
|
|
71
83
|
receiptId: String(payload['receipt_id'] ?? bundleId ?? payload['id'] ?? assetId ?? 'unknown'),
|
|
72
84
|
status: accepted ? 'accepted' : (decision === 'quarantine' ? 'quarantine' : 'rejected'),
|
|
@@ -80,12 +92,26 @@ export function publishRespToReceipt(status, body) {
|
|
|
80
92
|
// M8-1: 按语义而非纯状态码区分(都终态不重试 = money-safety: 不反复打经济端点).
|
|
81
93
|
// 402=creditShortage(余额不足) / 403=node 失效需 rebind / 409=duplicate / 422=payload 须修 / 429=cooldown.
|
|
82
94
|
const reasonByStatus = { 402: 'credit_shortage', 403: 'node_unauthorized', 409: 'duplicate', 422: 'invalid_payload', 429: 'cooldown' };
|
|
95
|
+
const rejectionCodeByStatus = {
|
|
96
|
+
400: 'invalid_request',
|
|
97
|
+
402: 'credit_shortage',
|
|
98
|
+
403: 'node_unauthorized',
|
|
99
|
+
404: 'not_found',
|
|
100
|
+
409: 'duplicate',
|
|
101
|
+
422: 'invalid_payload',
|
|
102
|
+
429: 'cooldown',
|
|
103
|
+
};
|
|
83
104
|
const receipt = {
|
|
84
105
|
receiptId: String(payload['receipt_id'] ?? 'rejected'),
|
|
85
106
|
status: 'rejected',
|
|
86
107
|
reason: String(payload['reason'] ?? reasonByStatus[status] ?? `hub ${status}`),
|
|
87
108
|
...(assetId ? { assetId } : {}),
|
|
109
|
+
...(assetIds ? { assetIds } : {}),
|
|
88
110
|
terminal: true,
|
|
111
|
+
rejection: {
|
|
112
|
+
code: rejectionCodeByStatus[status] ?? 'hub_rejected',
|
|
113
|
+
...(retryAfterMs !== undefined ? { retryAfterMs } : {}),
|
|
114
|
+
},
|
|
89
115
|
};
|
|
90
116
|
if (status === 402) {
|
|
91
117
|
receipt.economic = {
|
package/package.json
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evomap/evolver-adapter-public",
|
|
3
|
-
"version": "2.0.0
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": "^22.13.0 || >=23.4.0"
|
|
8
|
+
},
|
|
6
9
|
"description": "公版 hub 适配器 (积分/治理)",
|
|
7
10
|
"main": "./dist/index.js",
|
|
8
11
|
"types": "./dist/index.d.ts",
|
|
@@ -14,7 +17,7 @@
|
|
|
14
17
|
},
|
|
15
18
|
"dependencies": {
|
|
16
19
|
"@evomap/atp-sdk": "^0.1.0",
|
|
17
|
-
"@evomap/evolver-core": "2.0.0
|
|
20
|
+
"@evomap/evolver-core": "2.0.0",
|
|
18
21
|
"undici": "^6.27.0"
|
|
19
22
|
},
|
|
20
23
|
"optionalDependencies": {
|
|
@@ -26,7 +29,7 @@
|
|
|
26
29
|
},
|
|
27
30
|
"publishConfig": {
|
|
28
31
|
"access": "public",
|
|
29
|
-
"tag": "
|
|
32
|
+
"tag": "latest"
|
|
30
33
|
},
|
|
31
34
|
"files": [
|
|
32
35
|
"dist/",
|