@evomap/evolver-adapter-public 2.0.0-beta.9 → 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.
@@ -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
- let intervalMs = dc.intervalMs;
41
- for (;;) {
42
- const res = await this.opts.transport.pollToken(dc.deviceCode);
43
- if (!('pending' in res))
44
- return this.persist(res, fp);
45
- if (this.now() >= deadline)
46
- throw new Error('device_flow_timeout');
47
- await this.sleep(intervalMs);
48
- intervalMs += 1000; // gentle back-off; the hub's slow_down also surfaces as pending
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 res = await this.opts.transport.refresh(cred.refreshToken);
66
- return this.persist(res, fp, cred.refreshToken);
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
- async function postJson(path, body) {
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 res = await fetchFn(url, {
34
- method: 'POST',
35
- headers: { 'content-type': 'application/json' },
36
- body: JSON.stringify(body),
37
- });
38
- const json = (await res.json().catch(() => ({})));
39
- return { status: res.status, json };
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,3 @@
1
+ export declare const POWERSHELL_STDIN_SCRIPT_COMMAND = "& ([scriptblock]::Create([Console]::In.ReadToEnd()))";
2
+ export declare function windowsAclFailureDetail(cause: unknown): string;
3
+ export declare function stripPowerShellClixml(text: string): string;
@@ -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(/&lt;/g, '<')
60
+ .replace(/&gt;/g, '>')
61
+ .replace(/&quot;/g, String.fromCharCode(34))
62
+ .replace(/&apos;/g, String.fromCharCode(39))
63
+ .replace(/&amp;/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
+ }
@@ -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
@@ -57,7 +59,9 @@ export interface AccountAssetListResult {
57
59
  nextCursor?: string;
58
60
  }
59
61
  /** 完整 GEP-A2A 信封(实测 dev: publish/fetch/validate 等协议消息端点必须全信封, 非仅 protocol+message_type). */
60
- export declare function gepEnvelope(messageType: string, payload: unknown): Record<string, unknown>;
62
+ export declare function gepEnvelope(messageType: string, payload: unknown, options?: {
63
+ messageId?: string;
64
+ }): Record<string, unknown>;
61
65
  export interface PublicHubOptions {
62
66
  baseUrl: string;
63
67
  auth: hub.AuthProvider;
@@ -72,6 +76,8 @@ export interface PublicHelloResult {
72
76
  ok: boolean;
73
77
  authError?: boolean;
74
78
  nodeId?: string;
79
+ claimCode?: string;
80
+ claimUrl?: string;
75
81
  nodeSecretVersion?: number;
76
82
  rateLimitUntilMs?: number;
77
83
  error?: string;
@@ -122,6 +128,7 @@ export interface PublicHeartbeatResult {
122
128
  reason?: string;
123
129
  };
124
130
  forceUpdate?: PublicForceUpdateDirective;
131
+ capabilityGaps?: readonly string[];
125
132
  }
126
133
  export declare function isHubDryRunEnabled(env?: Record<string, string | undefined>): boolean;
127
134
  export declare function outboundMaxBodyBytes(env?: Record<string, string | undefined>): number;
@@ -135,15 +142,18 @@ export declare class PublicHubCapability implements hub.HubCapability {
135
142
  readonly auth: hub.AuthProvider;
136
143
  readonly recipes: hub.RecipeCapability;
137
144
  constructor(opts: PublicHubOptions);
145
+ private evolverVersionForWire;
146
+ private envFingerprintForWire;
138
147
  hello(opts: PublicHelloOptions): Promise<PublicHelloResult>;
139
148
  heartbeat(opts?: PublicHeartbeatOptions): Promise<PublicHeartbeatResult>;
140
149
  private heartbeatMeta;
141
- publish(bundle: hub.AssetRecord[]): Promise<hub.PublishReceipt>;
150
+ publish(bundle: hub.AssetRecord[], options?: hub.PublishOptions): Promise<hub.PublishReceipt>;
142
151
  fetch(query: hub.HubQuery): Promise<hub.AssetRecord[]>;
143
152
  fetchAssetById(assetId: string): Promise<hub.AssetRecord | null>;
144
153
  /**
145
154
  * #69: search != fetch. Free-text is the hub's vector endpoint (GET /a2a/assets/semantic-search?q=);
146
- * signals/id queries fall through to fetch. /a2a/fetch does NOT do semantic, so text must not go there.
155
+ * signal/id queries use the Hub's free search-only phase on /a2a/fetch. /a2a/fetch does NOT do semantic,
156
+ * so text must not go there and paid/full fetch must remain an explicit follow-up.
147
157
  */
148
158
  search(query: hub.HubQuery): Promise<hub.AssetRecord[]>;
149
159
  agentDirectory: hub.AgentDirectoryCapability;
@@ -159,6 +169,8 @@ export declare class PublicHubCapability implements hub.HubCapability {
159
169
  recordOutcome(report: OutcomeReport): Promise<OutcomeReceipt>;
160
170
  recordMemoryEvent(report: MemoryGraphEventReport): Promise<MemoryGraphEventReceipt>;
161
171
  recordReuseResult(report: hub.ReuseResultReport): Promise<hub.ReuseResultReceipt>;
172
+ listLearningAssets(options?: hub.LearningAssetListOptions): Promise<hub.LearningAssetListResult>;
173
+ recordLearningAssetUsage(report: hub.LearningAssetUsageReport): Promise<hub.LearningAssetUsageReceipt>;
162
174
  /**
163
175
  * Pre-publish dry-run (POST /a2a/validate). The hub runs the same hub-side quality +
164
176
  * content-safety gate as publish but stores nothing and charges no credits. This adapter is
@@ -171,7 +183,7 @@ export declare class PublicHubCapability implements hub.HubCapability {
171
183
  */
172
184
  validate(bundle: hub.AssetRecord[]): Promise<hub.ValidateReceipt>;
173
185
  createRecipe(request: hub.RecipeCreateRequest): Promise<hub.RecipeReceipt>;
174
- publishRecipe(recipeId: string): Promise<hub.RecipeReceipt>;
186
+ publishRecipe(recipeId: string, options?: hub.RecipePublishOptions): Promise<hub.RecipeReceipt>;
175
187
  getRecipe(recipeId: string): Promise<hub.RecipeFetchReceipt>;
176
188
  expressRecipe(recipeId: string, request?: hub.RecipeExpressRequest): Promise<hub.RecipeExpressionReceipt>;
177
189
  task: {