@evomap/evolver-adapter-public 2.0.0-beta.2 → 2.0.0-beta.22
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 +90 -3
- package/dist/auth/credentialStore.js +1096 -10
- package/dist/auth/legacyShim.d.ts +2 -2
- package/dist/auth/legacyShim.js +2 -2
- 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 +35 -9
- package/dist/hubCapability.js +593 -54
- package/dist/hubFetch.d.ts +47 -14
- package/dist/hubFetch.js +374 -79
- package/dist/hubReuse.d.ts +41 -0
- package/dist/hubReuse.js +328 -33
- 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 +48 -0
- package/dist/learningPacketSink.js +194 -0
- package/dist/wireMap.d.ts +3 -1
- package/dist/wireMap.js +29 -3
- package/package.json +9 -2
|
@@ -13,8 +13,8 @@ export type NodeSecretVersionHandler = (nodeSecretVersion?: number) => void;
|
|
|
13
13
|
*/
|
|
14
14
|
export type NodeSecretDivergenceHandler = () => void;
|
|
15
15
|
/**
|
|
16
|
-
* node_secret
|
|
17
|
-
*
|
|
16
|
+
* LegacyAuthShim exposes node_secret as a transport-neutral credential field. HubFetch promotes it to
|
|
17
|
+
* Authorization: Bearer for GET and strict GEP envelope endpoints before egress.
|
|
18
18
|
*/
|
|
19
19
|
export declare class LegacyAuthShim implements hub.AuthProvider {
|
|
20
20
|
private nodeSecret;
|
package/dist/auth/legacyShim.js
CHANGED
|
@@ -13,8 +13,8 @@ export function parseNodeSecretVersion(value) {
|
|
|
13
13
|
return Number.isSafeInteger(parsed) ? parsed : undefined;
|
|
14
14
|
}
|
|
15
15
|
/**
|
|
16
|
-
* node_secret
|
|
17
|
-
*
|
|
16
|
+
* LegacyAuthShim exposes node_secret as a transport-neutral credential field. HubFetch promotes it to
|
|
17
|
+
* Authorization: Bearer for GET and strict GEP envelope endpoints before egress.
|
|
18
18
|
*/
|
|
19
19
|
export class LegacyAuthShim {
|
|
20
20
|
nodeSecret;
|
|
@@ -15,11 +15,11 @@ export interface TokenResp {
|
|
|
15
15
|
}
|
|
16
16
|
/** 注入的 OAuth device flow 传输(M6-6 真 HTTP; 测试用桩). refresh 走 refresh_token grant. */
|
|
17
17
|
export interface OAuthTransport {
|
|
18
|
-
requestDeviceCode: (fingerprint: string) => Promise<DeviceCodeResp>;
|
|
19
|
-
pollToken: (deviceCode: string) => Promise<TokenResp | {
|
|
18
|
+
requestDeviceCode: (fingerprint: string, signal?: AbortSignal) => Promise<DeviceCodeResp>;
|
|
19
|
+
pollToken: (deviceCode: string, signal?: AbortSignal) => Promise<TokenResp | {
|
|
20
20
|
pending: true;
|
|
21
21
|
}>;
|
|
22
|
-
refresh?: (refreshToken: string) => Promise<TokenResp>;
|
|
22
|
+
refresh?: (refreshToken: string, signal?: AbortSignal) => Promise<TokenResp>;
|
|
23
23
|
}
|
|
24
24
|
export interface PublicOAuthOptions {
|
|
25
25
|
credPath: string;
|
|
@@ -32,6 +32,8 @@ export interface PublicOAuthOptions {
|
|
|
32
32
|
sleep?: (ms: number) => Promise<void>;
|
|
33
33
|
/** 整个 device flow 的最长等待(默认 15min); 超时抛 device_flow_timeout. */
|
|
34
34
|
maxWaitMs?: number;
|
|
35
|
+
/** Provider-level refresh bound, including injected transports. */
|
|
36
|
+
refreshTimeoutMs?: number;
|
|
35
37
|
}
|
|
36
38
|
/**
|
|
37
39
|
* device token 认证(M6-5, 默认路径 evolver login). device code flow(学 gh auth login):
|
|
@@ -45,6 +47,7 @@ export declare class PublicOAuthProvider implements hub.AuthProvider {
|
|
|
45
47
|
private readonly now;
|
|
46
48
|
private readonly sleep;
|
|
47
49
|
private readonly maxWaitMs;
|
|
50
|
+
private readonly refreshTimeoutMs;
|
|
48
51
|
constructor(opts: PublicOAuthOptions);
|
|
49
52
|
private fingerprint;
|
|
50
53
|
/**
|
|
@@ -54,9 +57,9 @@ export declare class PublicOAuthProvider implements hub.AuthProvider {
|
|
|
54
57
|
* itself (respecting the server interval); a terminal error from the transport
|
|
55
58
|
* (access_denied / expired_token) propagates.
|
|
56
59
|
*/
|
|
57
|
-
login(): Promise<hub.Credential>;
|
|
58
|
-
authenticate(): Promise<hub.SignedRequest>;
|
|
59
|
-
rotate(): Promise<hub.Credential>;
|
|
60
|
+
login(parentSignal?: AbortSignal): Promise<hub.Credential>;
|
|
61
|
+
authenticate(req?: hub.HttpRequestLike): Promise<hub.SignedRequest>;
|
|
62
|
+
rotate(parentSignal?: AbortSignal): Promise<hub.Credential>;
|
|
60
63
|
revoke(): Promise<void>;
|
|
61
64
|
private persist;
|
|
62
65
|
}
|
|
@@ -2,6 +2,43 @@ import { CredentialStore } from './credentialStore.js';
|
|
|
2
2
|
import { resolveMachineId, machineFingerprint } from './machineId.js';
|
|
3
3
|
export const TOKEN_TTL_MS = 24 * 60 * 60_000; // fallback only; the hub supplies expiresInMs (~1h)
|
|
4
4
|
export const ROTATE_BEFORE_MS = 5 * 60_000; // refresh 5min before expiry (hub access tokens are short-lived ~1h)
|
|
5
|
+
const DEFAULT_REFRESH_TIMEOUT_MS = 15_000;
|
|
6
|
+
function abortReason(signal) {
|
|
7
|
+
return signal.reason instanceof Error ? signal.reason : new Error('device_flow_timeout');
|
|
8
|
+
}
|
|
9
|
+
async function awaitWithAbort(promise, signal) {
|
|
10
|
+
const pending = Promise.resolve(promise);
|
|
11
|
+
if (signal.aborted) {
|
|
12
|
+
void pending.catch(() => { });
|
|
13
|
+
throw abortReason(signal);
|
|
14
|
+
}
|
|
15
|
+
return await new Promise((resolve, reject) => {
|
|
16
|
+
const cleanup = () => signal.removeEventListener('abort', onAbort);
|
|
17
|
+
const onAbort = () => {
|
|
18
|
+
cleanup();
|
|
19
|
+
reject(abortReason(signal));
|
|
20
|
+
};
|
|
21
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
22
|
+
void pending.then((value) => { cleanup(); resolve(value); }, (error) => { cleanup(); reject(error); });
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
function linkedTimeoutSignal(parent, timeoutMs, timeoutReason) {
|
|
26
|
+
const controller = new AbortController();
|
|
27
|
+
const abortFromParent = () => controller.abort(parent?.reason);
|
|
28
|
+
if (parent?.aborted)
|
|
29
|
+
abortFromParent();
|
|
30
|
+
else
|
|
31
|
+
parent?.addEventListener('abort', abortFromParent, { once: true });
|
|
32
|
+
const timeout = setTimeout(() => controller.abort(timeoutReason), timeoutMs);
|
|
33
|
+
timeout.unref?.();
|
|
34
|
+
return {
|
|
35
|
+
signal: controller.signal,
|
|
36
|
+
dispose: () => {
|
|
37
|
+
clearTimeout(timeout);
|
|
38
|
+
parent?.removeEventListener('abort', abortFromParent);
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
5
42
|
/**
|
|
6
43
|
* device token 认证(M6-5, 默认路径 evolver login). device code flow(学 gh auth login):
|
|
7
44
|
* 显示 user code → 用户在浏览器授权 → 轮询拿 token. 绑机器指纹(CEO: OS machineId+软兜底).
|
|
@@ -14,12 +51,14 @@ export class PublicOAuthProvider {
|
|
|
14
51
|
now;
|
|
15
52
|
sleep;
|
|
16
53
|
maxWaitMs;
|
|
54
|
+
refreshTimeoutMs;
|
|
17
55
|
constructor(opts) {
|
|
18
56
|
this.opts = opts;
|
|
19
57
|
this.store = new CredentialStore(opts.credPath);
|
|
20
58
|
this.now = opts.now ?? (() => Date.now());
|
|
21
59
|
this.sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
22
60
|
this.maxWaitMs = opts.maxWaitMs ?? 15 * 60_000;
|
|
61
|
+
this.refreshTimeoutMs = Math.max(1, opts.refreshTimeoutMs ?? DEFAULT_REFRESH_TIMEOUT_MS);
|
|
23
62
|
}
|
|
24
63
|
fingerprint() { return machineFingerprint(resolveMachineId(this.opts.machine).id); }
|
|
25
64
|
/**
|
|
@@ -29,43 +68,57 @@ export class PublicOAuthProvider {
|
|
|
29
68
|
* itself (respecting the server interval); a terminal error from the transport
|
|
30
69
|
* (access_denied / expired_token) propagates.
|
|
31
70
|
*/
|
|
32
|
-
async login() {
|
|
71
|
+
async login(parentSignal) {
|
|
33
72
|
const cached = this.store.load();
|
|
34
73
|
if (cached && 'token' in cached && (cached.expiresAt ?? 0) - ROTATE_BEFORE_MS > this.now())
|
|
35
74
|
return cached;
|
|
36
75
|
const fp = this.fingerprint();
|
|
37
|
-
const dc = await this.opts.transport.requestDeviceCode(fp);
|
|
38
|
-
(this.opts.onUserCode ?? ((d) => { process.stdout.write(`授权码 ${d.userCode} → ${d.verificationUri}\n`); }))(dc);
|
|
39
76
|
const deadline = this.now() + this.maxWaitMs;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
77
|
+
const deadlineSignal = linkedTimeoutSignal(parentSignal, Math.max(0, this.maxWaitMs), new Error('device_flow_timeout'));
|
|
78
|
+
try {
|
|
79
|
+
const dc = await awaitWithAbort(this.opts.transport.requestDeviceCode(fp, deadlineSignal.signal), deadlineSignal.signal);
|
|
80
|
+
(this.opts.onUserCode ?? ((d) => { process.stdout.write(`授权码 ${d.userCode} → ${d.verificationUri}\n`); }))(dc);
|
|
81
|
+
let intervalMs = dc.intervalMs;
|
|
82
|
+
for (;;) {
|
|
83
|
+
if (this.now() >= deadline)
|
|
84
|
+
throw new Error('device_flow_timeout');
|
|
85
|
+
const res = await awaitWithAbort(this.opts.transport.pollToken(dc.deviceCode, deadlineSignal.signal), deadlineSignal.signal);
|
|
86
|
+
if (!('pending' in res))
|
|
87
|
+
return this.persist(res, fp);
|
|
88
|
+
if (this.now() >= deadline)
|
|
89
|
+
throw new Error('device_flow_timeout');
|
|
90
|
+
await awaitWithAbort(this.sleep(intervalMs), deadlineSignal.signal);
|
|
91
|
+
intervalMs += 1000; // gentle back-off; the hub's slow_down also surfaces as pending
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
finally {
|
|
95
|
+
deadlineSignal.dispose();
|
|
49
96
|
}
|
|
50
97
|
}
|
|
51
|
-
async authenticate() {
|
|
98
|
+
async authenticate(req) {
|
|
52
99
|
let cred = this.store.load();
|
|
53
100
|
if (!cred || !('token' in cred))
|
|
54
101
|
throw new Error('oauth 未登录, 先 login()');
|
|
55
102
|
if ((cred.expiresAt ?? 0) - ROTATE_BEFORE_MS <= this.now())
|
|
56
|
-
cred = await this.rotate();
|
|
103
|
+
cred = await this.rotate(req?.signal);
|
|
57
104
|
return { headers: { authorization: `Bearer ${cred.token}`, 'x-evomap-device': cred.device ?? '' } };
|
|
58
105
|
}
|
|
59
|
-
async rotate() {
|
|
106
|
+
async rotate(parentSignal) {
|
|
60
107
|
const cred = this.store.load();
|
|
61
108
|
const fp = this.fingerprint();
|
|
62
109
|
// Prefer a seamless refresh_token rotation; fall back to a fresh device-flow
|
|
63
110
|
// login only when there is no usable refresh token (or no refresh transport).
|
|
64
111
|
if (cred && 'refreshToken' in cred && cred.refreshToken && this.opts.transport.refresh) {
|
|
65
|
-
const
|
|
66
|
-
|
|
112
|
+
const refreshSignal = linkedTimeoutSignal(parentSignal, this.refreshTimeoutMs, new Error('oauth_refresh_timeout'));
|
|
113
|
+
try {
|
|
114
|
+
const res = await awaitWithAbort(this.opts.transport.refresh(cred.refreshToken, refreshSignal.signal), refreshSignal.signal);
|
|
115
|
+
return this.persist(res, fp, cred.refreshToken);
|
|
116
|
+
}
|
|
117
|
+
finally {
|
|
118
|
+
refreshSignal.dispose();
|
|
119
|
+
}
|
|
67
120
|
}
|
|
68
|
-
return this.login();
|
|
121
|
+
return this.login(parentSignal);
|
|
69
122
|
}
|
|
70
123
|
async revoke() { }
|
|
71
124
|
// fallbackRefresh: carry the prior refresh token forward when the hub's refresh
|
|
@@ -16,6 +16,10 @@ export interface OAuthHttpTransportOptions {
|
|
|
16
16
|
scopes?: string[];
|
|
17
17
|
/** Injected for tests; defaults to the secure global fetch (https guard + forced TLS). */
|
|
18
18
|
fetchFn?: FetchLike;
|
|
19
|
+
/** Per-request deadline. The device-flow provider separately enforces its total deadline. */
|
|
20
|
+
timeoutMs?: number;
|
|
21
|
+
/** Defensive response cap; configurable only to keep boundary tests small. */
|
|
22
|
+
maxResponseBytes?: number;
|
|
19
23
|
}
|
|
20
24
|
/**
|
|
21
25
|
* Real HTTP transport (M6-6) for the RFC 8628 device authorization grant. The
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { assertHubUrlSecure, globalFetchLike } from '../hubFetch.js';
|
|
1
|
+
import { assertHubUrlSecure, drainHubResponse, globalFetchLike, HUB_GENERAL_TIMEOUT_MS, HUB_JSON_TEXT_MAX_BYTES, readHubResponseJson, } from '../hubFetch.js';
|
|
2
2
|
const DEVICE_GRANT = 'urn:ietf:params:oauth:grant-type:device_code';
|
|
3
3
|
/** First-party public client seeded on the hub (scripts/seed-evolver-oauth-client.js). */
|
|
4
4
|
export const DEFAULT_CLIENT_ID = 'evolver-cli';
|
|
@@ -27,26 +27,77 @@ export function createOAuthHttpTransport(opts) {
|
|
|
27
27
|
const clientId = opts.clientId ?? DEFAULT_CLIENT_ID;
|
|
28
28
|
const scope = (opts.scopes ?? DEFAULT_SCOPES).join(' ');
|
|
29
29
|
const fetchFn = opts.fetchFn ?? globalFetchLike;
|
|
30
|
-
|
|
30
|
+
const timeoutMs = Math.max(1, opts.timeoutMs ?? HUB_GENERAL_TIMEOUT_MS);
|
|
31
|
+
const maxResponseBytes = Math.max(1, opts.maxResponseBytes ?? HUB_JSON_TEXT_MAX_BYTES);
|
|
32
|
+
async function postJson(path, body, parentSignal) {
|
|
31
33
|
const url = `${base}${path}`;
|
|
32
34
|
assertHubUrlSecure(url); // defense in depth; also enforced inside globalFetchLike
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
const controller = new AbortController();
|
|
36
|
+
const timeoutError = new Error(`${path} timed out after ${timeoutMs}ms`);
|
|
37
|
+
const timeout = setTimeout(() => controller.abort(timeoutError), timeoutMs);
|
|
38
|
+
timeout.unref?.();
|
|
39
|
+
const onParentAbort = () => controller.abort(parentSignal?.reason instanceof Error ? parentSignal.reason : new Error('device_flow_timeout'));
|
|
40
|
+
if (parentSignal?.aborted)
|
|
41
|
+
onParentAbort();
|
|
42
|
+
else
|
|
43
|
+
parentSignal?.addEventListener('abort', onParentAbort, { once: true });
|
|
44
|
+
const awaitRequest = async (promise) => {
|
|
45
|
+
const pending = Promise.resolve(promise);
|
|
46
|
+
if (controller.signal.aborted) {
|
|
47
|
+
void pending.catch(() => { });
|
|
48
|
+
throw controller.signal.reason;
|
|
49
|
+
}
|
|
50
|
+
return await new Promise((resolve, reject) => {
|
|
51
|
+
const cleanup = () => controller.signal.removeEventListener('abort', onAbort);
|
|
52
|
+
const onAbort = () => { cleanup(); reject(controller.signal.reason); };
|
|
53
|
+
controller.signal.addEventListener('abort', onAbort, { once: true });
|
|
54
|
+
void pending.then((value) => { cleanup(); resolve(value); }, (error) => { cleanup(); reject(error); });
|
|
55
|
+
});
|
|
56
|
+
};
|
|
57
|
+
try {
|
|
58
|
+
const res = await awaitRequest(fetchFn(url, {
|
|
59
|
+
method: 'POST',
|
|
60
|
+
headers: { 'content-type': 'application/json' },
|
|
61
|
+
body: JSON.stringify(body),
|
|
62
|
+
redirect: 'manual',
|
|
63
|
+
signal: controller.signal,
|
|
64
|
+
}));
|
|
65
|
+
if (res.status >= 300 && res.status < 400) {
|
|
66
|
+
await drainHubResponse(res, { signal: controller.signal });
|
|
67
|
+
if (controller.signal.aborted)
|
|
68
|
+
throw controller.signal.reason;
|
|
69
|
+
throw new Error(`${path} refused an unexpected Hub redirect (HTTP ${res.status})`);
|
|
70
|
+
}
|
|
71
|
+
let json;
|
|
72
|
+
try {
|
|
73
|
+
json = await awaitRequest(readHubResponseJson(res, {
|
|
74
|
+
maxBytes: maxResponseBytes,
|
|
75
|
+
signal: controller.signal,
|
|
76
|
+
}));
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
if (controller.signal.aborted)
|
|
80
|
+
throw controller.signal.reason;
|
|
81
|
+
if (!(error instanceof SyntaxError))
|
|
82
|
+
throw error;
|
|
83
|
+
json = {};
|
|
84
|
+
}
|
|
85
|
+
return { status: res.status, json };
|
|
86
|
+
}
|
|
87
|
+
finally {
|
|
88
|
+
clearTimeout(timeout);
|
|
89
|
+
parentSignal?.removeEventListener('abort', onParentAbort);
|
|
90
|
+
}
|
|
40
91
|
}
|
|
41
92
|
return {
|
|
42
|
-
async requestDeviceCode(_fingerprint) {
|
|
93
|
+
async requestDeviceCode(_fingerprint, signal) {
|
|
43
94
|
// The hub binds the device authorization to the client + the approving
|
|
44
95
|
// user, not to a machine fingerprint, so the fingerprint is not sent here;
|
|
45
96
|
// it is bound locally on the resulting credential (x-evomap-device header).
|
|
46
97
|
const { status, json } = await postJson('/oauth/device_authorization', {
|
|
47
98
|
client_id: clientId,
|
|
48
99
|
scope,
|
|
49
|
-
});
|
|
100
|
+
}, signal);
|
|
50
101
|
if (status >= 400 || !json.device_code || !json.user_code) {
|
|
51
102
|
throw new Error(`device_authorization failed (HTTP ${status}): ${json.error ?? 'no device_code'}`);
|
|
52
103
|
}
|
|
@@ -57,12 +108,12 @@ export function createOAuthHttpTransport(opts) {
|
|
|
57
108
|
intervalMs: (json.interval ?? 5) * 1000,
|
|
58
109
|
};
|
|
59
110
|
},
|
|
60
|
-
async pollToken(deviceCode) {
|
|
111
|
+
async pollToken(deviceCode, signal) {
|
|
61
112
|
const { status, json } = await postJson('/oauth/token', {
|
|
62
113
|
grant_type: DEVICE_GRANT,
|
|
63
114
|
device_code: deviceCode,
|
|
64
115
|
client_id: clientId,
|
|
65
|
-
});
|
|
116
|
+
}, signal);
|
|
66
117
|
if (json.access_token) {
|
|
67
118
|
return tokenResp(json);
|
|
68
119
|
}
|
|
@@ -71,12 +122,12 @@ export function createOAuthHttpTransport(opts) {
|
|
|
71
122
|
}
|
|
72
123
|
throw new Error(`token poll failed (HTTP ${status}): ${json.error ?? 'unknown'}`);
|
|
73
124
|
},
|
|
74
|
-
async refresh(refreshToken) {
|
|
125
|
+
async refresh(refreshToken, signal) {
|
|
75
126
|
const { status, json } = await postJson('/oauth/token', {
|
|
76
127
|
grant_type: 'refresh_token',
|
|
77
128
|
refresh_token: refreshToken,
|
|
78
129
|
client_id: clientId,
|
|
79
|
-
});
|
|
130
|
+
}, signal);
|
|
80
131
|
if (!json.access_token) {
|
|
81
132
|
throw new Error(`refresh failed (HTTP ${status}): ${json.error ?? 'no access_token'}`);
|
|
82
133
|
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
export const POWERSHELL_STDIN_SCRIPT_COMMAND = '& ([scriptblock]::Create([Console]::In.ReadToEnd()))';
|
|
2
|
+
export function windowsAclFailureDetail(cause) {
|
|
3
|
+
const error = cause;
|
|
4
|
+
const parts = [];
|
|
5
|
+
if (typeof error?.code === 'string' && error.code) {
|
|
6
|
+
parts.push('code ' + normalizePowerShellDiagnostic(error.code));
|
|
7
|
+
}
|
|
8
|
+
if (typeof error?.status === 'number')
|
|
9
|
+
parts.push('exit ' + error.status);
|
|
10
|
+
if (typeof error?.signal === 'string' && error.signal) {
|
|
11
|
+
parts.push('signal ' + normalizePowerShellDiagnostic(error.signal));
|
|
12
|
+
}
|
|
13
|
+
const streams = [error?.stderr, error?.stdout]
|
|
14
|
+
.map((stream) => (typeof stream === 'string'
|
|
15
|
+
? stream
|
|
16
|
+
: Buffer.isBuffer(stream) ? stream.toString('utf8') : ''))
|
|
17
|
+
.map(stripPowerShellClixml)
|
|
18
|
+
.filter((text) => text.length > 0);
|
|
19
|
+
if (streams.length > 0) {
|
|
20
|
+
parts.push(streams.join(' | '));
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
const message = typeof error?.message === 'string' ? error.message : '';
|
|
24
|
+
const detail = stripPowerShellClixml(message.replace(/-EncodedCommand\s+\S+/g, '-EncodedCommand <omitted>'));
|
|
25
|
+
if (detail)
|
|
26
|
+
parts.push(detail);
|
|
27
|
+
}
|
|
28
|
+
return boundPowerShellDiagnostic(parts.join('; '));
|
|
29
|
+
}
|
|
30
|
+
export function stripPowerShellClixml(text) {
|
|
31
|
+
if (/#<\s*CLIXML\b/i.test(text)) {
|
|
32
|
+
const errorRecords = [];
|
|
33
|
+
const serializedErrors = /<S\b(?=[^>]*\bS=\x22Error\x22)[^>]*>([\s\S]*?)<\/S>/gi;
|
|
34
|
+
for (const match of text.matchAll(serializedErrors)) {
|
|
35
|
+
const record = decodePowerShellClixmlText(match[1] ?? '');
|
|
36
|
+
const hasLocationBoilerplate = /(?:^|\s)At line:\d+ char:\d+/i.test(record);
|
|
37
|
+
const decoded = normalizePowerShellDiagnostic(stripPowerShellLocationBoilerplate(record));
|
|
38
|
+
if (decoded)
|
|
39
|
+
errorRecords.push(decoded);
|
|
40
|
+
if (hasLocationBoilerplate)
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
if (errorRecords.length > 0)
|
|
44
|
+
return [...new Set(errorRecords)].join(' | ');
|
|
45
|
+
}
|
|
46
|
+
return normalizePowerShellDiagnostic(stripPowerShellLocationBoilerplate(text)
|
|
47
|
+
.replace(/<Objs\b[\s\S]*?<\/Objs>/g, ' ')
|
|
48
|
+
.replace(/<Objs\b[^\n]*/g, ' ')
|
|
49
|
+
.replace(/#<\s*CLIXML\b/gi, ' '));
|
|
50
|
+
}
|
|
51
|
+
function stripPowerShellLocationBoilerplate(text) {
|
|
52
|
+
return text.replace(/(?:^|\s)At line:\d+ char:\d+[\s\S]*$/i, ' ');
|
|
53
|
+
}
|
|
54
|
+
function decodePowerShellClixmlText(text) {
|
|
55
|
+
return text
|
|
56
|
+
.replace(/_x([0-9a-f]{4})_/gi, (_match, hex) => String.fromCharCode(Number.parseInt(hex, 16)))
|
|
57
|
+
.replace(/&#x([0-9a-f]+);/gi, (match, value) => decodeXmlCodePoint(match, value, 16))
|
|
58
|
+
.replace(/&#([0-9]+);/g, (match, value) => decodeXmlCodePoint(match, value, 10))
|
|
59
|
+
.replace(/</g, '<')
|
|
60
|
+
.replace(/>/g, '>')
|
|
61
|
+
.replace(/"/g, String.fromCharCode(34))
|
|
62
|
+
.replace(/'/g, String.fromCharCode(39))
|
|
63
|
+
.replace(/&/g, '&');
|
|
64
|
+
}
|
|
65
|
+
function decodeXmlCodePoint(match, value, radix) {
|
|
66
|
+
const codePoint = Number.parseInt(value, radix);
|
|
67
|
+
if (!Number.isInteger(codePoint) || codePoint < 0 || codePoint > 0x10ffff ||
|
|
68
|
+
(codePoint >= 0xd800 && codePoint <= 0xdfff)) {
|
|
69
|
+
return match;
|
|
70
|
+
}
|
|
71
|
+
return String.fromCodePoint(codePoint);
|
|
72
|
+
}
|
|
73
|
+
const POWERSHELL_DIAGNOSTIC_FORMAT_CHARACTER = /^\p{Cf}$/u;
|
|
74
|
+
function normalizePowerShellDiagnostic(text) {
|
|
75
|
+
const sanitized = [];
|
|
76
|
+
for (const character of text) {
|
|
77
|
+
const codePoint = character.codePointAt(0) ?? 0;
|
|
78
|
+
const isControl = codePoint <= 0x1f || (codePoint >= 0x7f && codePoint <= 0x9f);
|
|
79
|
+
const isFormat = POWERSHELL_DIAGNOSTIC_FORMAT_CHARACTER.test(character);
|
|
80
|
+
sanitized.push(isControl || isFormat || codePoint === 0x2028 || codePoint === 0x2029 ? ' ' : character);
|
|
81
|
+
}
|
|
82
|
+
return sanitized.join('').replace(/\s+/g, ' ').trim();
|
|
83
|
+
}
|
|
84
|
+
function boundPowerShellDiagnostic(text) {
|
|
85
|
+
const limit = 300;
|
|
86
|
+
if (text.length <= limit)
|
|
87
|
+
return text;
|
|
88
|
+
const headLength = Math.ceil((limit - 3) / 2);
|
|
89
|
+
const tailLength = limit - 3 - headLength;
|
|
90
|
+
return text.slice(0, headLength) + '...' + text.slice(-tailLength);
|
|
91
|
+
}
|
package/dist/hubCapability.d.ts
CHANGED
|
@@ -8,6 +8,8 @@ export declare const PUBLIC_PROTOCOL_VERSION = "gep-a2a/1.0.0";
|
|
|
8
8
|
export declare const PUBLIC_HUB_CAPABILITIES: hub.HubCapabilityName[];
|
|
9
9
|
export declare const USED_ASSET_IDS_MAX = 50;
|
|
10
10
|
export declare const USED_ASSET_ID_MAX_LEN = 200;
|
|
11
|
+
export declare const LEARNING_ASSET_IDS_MAX = 50;
|
|
12
|
+
export declare const LEARNING_ASSET_ID_MAX_LEN = 128;
|
|
11
13
|
/**
|
|
12
14
|
* An outcome the agent reports back to the hub's memory graph after a cycle.
|
|
13
15
|
* `usedAssetIds` is the fetch->outcome attribution claim: which hub assets the
|
|
@@ -56,8 +58,13 @@ export interface AccountAssetListResult {
|
|
|
56
58
|
hasMore: boolean;
|
|
57
59
|
nextCursor?: string;
|
|
58
60
|
}
|
|
61
|
+
export declare class MalformedAccountAssetPageError extends Error {
|
|
62
|
+
constructor();
|
|
63
|
+
}
|
|
59
64
|
/** 完整 GEP-A2A 信封(实测 dev: publish/fetch/validate 等协议消息端点必须全信封, 非仅 protocol+message_type). */
|
|
60
|
-
export declare function gepEnvelope(messageType: string, payload: unknown
|
|
65
|
+
export declare function gepEnvelope(messageType: string, payload: unknown, options?: {
|
|
66
|
+
messageId?: string;
|
|
67
|
+
}): Record<string, unknown>;
|
|
61
68
|
export interface PublicHubOptions {
|
|
62
69
|
baseUrl: string;
|
|
63
70
|
auth: hub.AuthProvider;
|
|
@@ -72,6 +79,8 @@ export interface PublicHelloResult {
|
|
|
72
79
|
ok: boolean;
|
|
73
80
|
authError?: boolean;
|
|
74
81
|
nodeId?: string;
|
|
82
|
+
claimCode?: string;
|
|
83
|
+
claimUrl?: string;
|
|
75
84
|
nodeSecretVersion?: number;
|
|
76
85
|
rateLimitUntilMs?: number;
|
|
77
86
|
error?: string;
|
|
@@ -81,6 +90,12 @@ export interface PublicHelloResult {
|
|
|
81
90
|
httpStatus?: number;
|
|
82
91
|
secretDiverged?: boolean;
|
|
83
92
|
}
|
|
93
|
+
export interface PublicHelloOptions {
|
|
94
|
+
rotate: boolean;
|
|
95
|
+
evolverVersion?: string;
|
|
96
|
+
/** Keep trust probes from adopting or clearing local credential state. */
|
|
97
|
+
preserveCredentials?: boolean;
|
|
98
|
+
}
|
|
84
99
|
export interface PublicHeartbeatOptions {
|
|
85
100
|
evolverVersion?: string;
|
|
86
101
|
lastUpdate?: PublicLastUpdatePayload;
|
|
@@ -116,6 +131,7 @@ export interface PublicHeartbeatResult {
|
|
|
116
131
|
reason?: string;
|
|
117
132
|
};
|
|
118
133
|
forceUpdate?: PublicForceUpdateDirective;
|
|
134
|
+
capabilityGaps?: readonly string[];
|
|
119
135
|
}
|
|
120
136
|
export declare function isHubDryRunEnabled(env?: Record<string, string | undefined>): boolean;
|
|
121
137
|
export declare function outboundMaxBodyBytes(env?: Record<string, string | undefined>): number;
|
|
@@ -129,18 +145,24 @@ export declare class PublicHubCapability implements hub.HubCapability {
|
|
|
129
145
|
readonly auth: hub.AuthProvider;
|
|
130
146
|
readonly recipes: hub.RecipeCapability;
|
|
131
147
|
constructor(opts: PublicHubOptions);
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}): Promise<PublicHelloResult>;
|
|
148
|
+
private evolverVersionForWire;
|
|
149
|
+
private envFingerprintForWire;
|
|
150
|
+
hello(opts: PublicHelloOptions): Promise<PublicHelloResult>;
|
|
136
151
|
heartbeat(opts?: PublicHeartbeatOptions): Promise<PublicHeartbeatResult>;
|
|
137
152
|
private heartbeatMeta;
|
|
138
|
-
publish(bundle: hub.AssetRecord[]): Promise<hub.PublishReceipt>;
|
|
153
|
+
publish(bundle: hub.AssetRecord[], options?: hub.PublishOptions): Promise<hub.PublishReceipt>;
|
|
139
154
|
fetch(query: hub.HubQuery): Promise<hub.AssetRecord[]>;
|
|
140
|
-
|
|
155
|
+
/**
|
|
156
|
+
* Fetch one asset AND say why, when the answer is not an asset. `fetchAssetById` collapses every outcome to
|
|
157
|
+
* `null`, so a caller could not tell "the hub does not have this" from "the hub delivered something the
|
|
158
|
+
* client refuses" — and the CLI reported both as `not_found` on assets that demonstrably exist (#964).
|
|
159
|
+
*/
|
|
160
|
+
fetchAssetDeliveryById(assetId: string, options?: hub.FetchAssetByIdOptions): Promise<hub.AssetDeliveryOutcome>;
|
|
161
|
+
fetchAssetById(assetId: string, options?: hub.FetchAssetByIdOptions): Promise<hub.AssetRecord | null>;
|
|
141
162
|
/**
|
|
142
163
|
* #69: search != fetch. Free-text is the hub's vector endpoint (GET /a2a/assets/semantic-search?q=);
|
|
143
|
-
*
|
|
164
|
+
* signal/id queries use the Hub's free search-only phase on /a2a/fetch. /a2a/fetch does NOT do semantic,
|
|
165
|
+
* so text must not go there and paid/full fetch must remain an explicit follow-up.
|
|
144
166
|
*/
|
|
145
167
|
search(query: hub.HubQuery): Promise<hub.AssetRecord[]>;
|
|
146
168
|
agentDirectory: hub.AgentDirectoryCapability;
|
|
@@ -156,6 +178,8 @@ export declare class PublicHubCapability implements hub.HubCapability {
|
|
|
156
178
|
recordOutcome(report: OutcomeReport): Promise<OutcomeReceipt>;
|
|
157
179
|
recordMemoryEvent(report: MemoryGraphEventReport): Promise<MemoryGraphEventReceipt>;
|
|
158
180
|
recordReuseResult(report: hub.ReuseResultReport): Promise<hub.ReuseResultReceipt>;
|
|
181
|
+
listLearningAssets(options?: hub.LearningAssetListOptions): Promise<hub.LearningAssetListResult>;
|
|
182
|
+
recordLearningAssetUsage(report: hub.LearningAssetUsageReport): Promise<hub.LearningAssetUsageReceipt>;
|
|
159
183
|
/**
|
|
160
184
|
* Pre-publish dry-run (POST /a2a/validate). The hub runs the same hub-side quality +
|
|
161
185
|
* content-safety gate as publish but stores nothing and charges no credits. This adapter is
|
|
@@ -168,8 +192,10 @@ export declare class PublicHubCapability implements hub.HubCapability {
|
|
|
168
192
|
*/
|
|
169
193
|
validate(bundle: hub.AssetRecord[]): Promise<hub.ValidateReceipt>;
|
|
170
194
|
createRecipe(request: hub.RecipeCreateRequest): Promise<hub.RecipeReceipt>;
|
|
171
|
-
publishRecipe(recipeId: string): Promise<hub.RecipeReceipt>;
|
|
195
|
+
publishRecipe(recipeId: string, options?: hub.RecipePublishOptions): Promise<hub.RecipeReceipt>;
|
|
172
196
|
getRecipe(recipeId: string): Promise<hub.RecipeFetchReceipt>;
|
|
197
|
+
searchRecipes(request?: hub.RecipeSearchRequest): Promise<hub.RecipeSearchReceipt>;
|
|
198
|
+
listRecipes(request?: hub.RecipeSearchRequest): Promise<hub.RecipeSearchReceipt>;
|
|
173
199
|
expressRecipe(recipeId: string, request?: hub.RecipeExpressRequest): Promise<hub.RecipeExpressionReceipt>;
|
|
174
200
|
task: {
|
|
175
201
|
claim: (taskId: string) => Promise<{
|