Files
2026-09-16 19:33:47 +08:00

2789 lines
128 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { createHash, randomUUID } from 'node:crypto';
import { dirname, join } from 'node:path';
import { reference, mailbox, hub as hubNs, shadow as shadow_, assetstore, wire, util, verify } from '@evomap/evolver-core';
import { AuthError, HubClientError, HubUnreachableError } from '@evomap/evolver-adapter-public';
import { SyncEngine, SYNC_INTERVALS } from '../sync/engine.js';
import { LifecycleManager } from '../lifecycle/manager.js';
import { executeForceUpdate } from '../selfUpdate/executor.js';
import { reportPendingSelfUpdateLastUpdate, reportSelfUpdateLastUpdate } from '../selfUpdate/lastUpdate.js';
import { backfillProxyTraceUploads } from '../llm/traceBackfill.js';
import { hubAuthFailureHint } from './selectHub.js';
import { CollaborationFacade } from './collaborationFacade.js';
import { PublishRecallVerifier, resolvePublishRecallConfig, } from './publishRecallVerifier.js';
const DEFAULT_PUBLISH_EXECUTION_VERIFY_TIMEOUT_MS = 30_000;
export const DEFAULT_IPC_PORT = 19820;
// V1 local-proxy compatibility contract; independent of the V2 mailbox envelope schema.
const PROXY_PROTOCOL_VERSION = '0.1.0';
const PROXY_STATUS_SCHEMA_VERSION = 1;
const MAX_TIMER_DELAY_MS = 2_147_483_647;
const MAX_PROXY_TICK_ERROR_LENGTH = 2_000;
const MAX_HEARTBEAT_TICK_ERROR_LENGTH = 1_000;
const MAX_EPHEMERAL_IPC_LISTEN_ATTEMPTS = 5;
const DEFAULT_ASSET_SEARCH_CACHE_TTL_MS = 30_000;
const DEFAULT_ASSET_SEARCH_CACHE_MAX = 256;
const DEFAULT_ASSET_SEARCH_STALE_GRACE_MS = 5 * 60_000;
const MAX_ASSET_SUBMIT_ITEMS = 50;
const ASYNC_ASSET_SUBMIT_REQUEST_ID = /^[A-Za-z0-9._:-]{1,128}$/;
const ASYNC_ASSET_SUBMIT_PREFIX = 'async_asset_submit:';
const OUTBOUND_HUB_MODE_FIELD = '__evolver_hub_mode';
const SYNC_ASSET_SUBMIT_PREFIX = 'sync_asset_submit:';
const SYNC_ASSET_SUBMIT_TYPE_RANK = {
Gene: 0,
Capsule: 1,
EvolutionEvent: 2,
AntiGene: 3,
};
const SYNC_ASSET_SUBMIT_SCOPE_STATE_KEY = 'sync_asset_submit:idempotency_scope:v1';
const SYNC_ASSET_SUBMIT_DIRECT_RETRY_GRACE_MS = 30_000;
const DEFAULT_SYNC_ASSET_SUBMIT_RESPONSE_TIMEOUT_MS = 15_000;
const HUB_AUTH_STATUS_STATE_KEY = 'hub:auth_status';
const HUB_AUTH_FAILED = 'auth_failed';
const HUB_AUTH_FAILURE_WARNING = 'Hub credential was revoked or rejected; only explicit local fallback is available.';
export function resolveHubAuthFailurePolicy(env = process.env) {
const configured = env['EVOLVER_HUB_AUTH_FAILURE_POLICY']?.trim().toLowerCase();
if (!configured)
return 'deny';
if (configured === 'deny' || configured === 'warn')
return configured;
throw new Error('EVOLVER_HUB_AUTH_FAILURE_POLICY must be deny or warn');
}
function withSynchronousPublishStatus(value, publishStatus) {
const body = isRecordValue(value) ? { ...value } : { receipt: value };
return { ...body, publish_status: publishStatus, queued: false };
}
function legacySynchronousResultStatus(result) {
if (result.ok)
return 'accepted';
// 202 表示兼容层已持久化但尚未完成;校验或终态/传输失败即使在 mailbox
// 中保留可重试意图,聚合仍归类为 failed。
return result.statusCode === 202 || result.error === 'publish_pending' ? 'pending' : 'failed';
}
function summarizeLegacySynchronousResults(results) {
const statuses = new Set(results.map(legacySynchronousResultStatus));
// publish_status 表示聚合终态,而 queued 独立表示是否仍有 durable mailbox 工作。
// 因此 pending+failed 必须是 failed/true:不能掩盖失败,也不能谎报没有排队。
if (statuses.has('failed') || statuses.size === 0)
return { publishStatus: 'failed', queued: statuses.has('pending') };
if (statuses.has('pending'))
return { publishStatus: 'pending', queued: true };
return { publishStatus: 'accepted', queued: false };
}
class OutboundHubModeMismatchError extends Error {
retryable = true;
retryAfterMs = 1_000;
constructor(expected, actual) {
super(`asset_submit hub mode mismatch: queued for ${expected}, running in ${actual}`);
this.name = 'OutboundHubModeMismatchError';
}
}
/**
* ProxyDaemon(M6-4) 装配层: 把 core(MailboxStore/Dispatcher/MailboxDaemon/IpcServer) +
* HubBindings(M6-1) + SyncEngine(M6-2) + LifecycleManager(M6-3) 拼成系统级 proxy.
* 职责分工(避免双 claim): MailboxDaemon 只 pump 'core'(本地确定性); proxy 出站归 SyncEngine.syncOutbound;
* inbound 由 SyncEngine.syncInbound 从 hub 拉; agent 消息留给 runtime 经 IPC claim.
*/
export class ProxyDaemon {
deps;
store;
dispatcher;
daemon;
sync;
lifecycle;
assetStore;
benchmark;
sourceProvenance;
sourceReview;
referenceStore;
remoteAssetById;
reuseResultReporter;
validator;
atp;
collaborationFacade;
publishRecallVerifier;
proxyHandler;
hub;
recipeComposeStarted = new Set();
ipc;
now;
random;
assetSearchCacheTtlMs;
assetSearchCacheMax;
assetSearchStaleGraceMs;
assetSubmitResponseTimeoutMs;
hubAuthFailurePolicy;
synchronousAssetSubmitScope;
shadowMode;
assetSearchCache = new Map();
assetSearchInflight = new Map();
synchronousAssetSubmitInflight = new Map();
assetSearchCooldownUntil = 0;
nextHeartbeatAt;
heartbeatFailures = 0;
heartbeatGeneration = 0;
/** Resolver for an in-flight runner sleep(); set while sleeping, called to wake early on poke. */
wakeRunnerResolve;
/** A poke that arrived between ticks (no sleep in flight) parks the wake here so it is not lost. */
wakeRunnerPending = false;
started = false;
lifecycleArmed = false;
lastTickAt;
nextTickDueAt;
consecutiveTickFailures = 0;
storeClosed = false;
forceUpdateTriggerInFlight = false;
forceUpdateLastTriggeredAt;
forceUpdateLastTriggeredKey;
pendingForceUpdateDirective;
forceUpdateTimer;
scheduledForceUpdateKey;
traceBackfillDraining = false;
loopWakeHandler;
/** 守护进程停止时取消宿主验证。 */
publishAbortController = new AbortController();
constructor(deps) {
this.deps = deps;
this.benchmark = assetstore.benchmarkContext(deps.benchmarkId ?? process.env['EVOLVER_BENCHMARK_ID']);
this.now = deps.now ?? (() => Date.now());
this.random = deps.random ?? Math.random;
this.assetSearchCacheTtlMs = positiveIntegerOr(deps.assetSearchCacheTtlMs, DEFAULT_ASSET_SEARCH_CACHE_TTL_MS);
this.assetSearchCacheMax = positiveIntegerOr(deps.assetSearchCacheMax, DEFAULT_ASSET_SEARCH_CACHE_MAX);
this.assetSearchStaleGraceMs = positiveIntegerOr(deps.assetSearchStaleGraceMs, DEFAULT_ASSET_SEARCH_STALE_GRACE_MS);
this.hubAuthFailurePolicy = deps.hubAuthFailurePolicy ?? 'deny';
this.assetSubmitResponseTimeoutMs = positiveIntegerOr(deps.assetSubmitResponseTimeoutMs, DEFAULT_SYNC_ASSET_SUBMIT_RESPONSE_TIMEOUT_MS);
if (!deps.store && !deps.storePath)
throw new Error('ProxyDaemon: 需 store 或 storePath 之一');
const shadow = deps.shadowMode === 'shadow';
this.shadowMode = shadow;
if (shadow && !deps.shadowSink)
throw new Error('ProxyDaemon: shadow 模式需 shadowSink');
// M8 shadow 装配: 在边界包 decorator, 下游 makeHubBindings/Dispatcher/SyncEngine/MailboxDaemon 零改.
this.store = deps.store
?? (shadow ? new shadow_.ShadowMailboxStore({ path: deps.storePath }, deps.shadowSink, 'shadow') : new mailbox.MailboxStore({ path: deps.storePath }));
const existingSynchronousAssetSubmitScope = this.store.getState(SYNC_ASSET_SUBMIT_SCOPE_STATE_KEY);
this.synchronousAssetSubmitScope = existingSynchronousAssetSubmitScope ?? randomUUID();
if (!existingSynchronousAssetSubmitScope) {
this.store.setState(SYNC_ASSET_SUBMIT_SCOPE_STATE_KEY, this.synchronousAssetSubmitScope);
}
const assetStoreDir = deps.assetStoreDir ?? (deps.storePath ? join(dirname(deps.storePath), 'assets') : undefined);
this.assetStore = deps.assetStore ?? (assetStoreDir ? new assetstore.LocalJsonlProvider(assetStoreDir) : undefined);
this.sourceProvenance = deps.sourceProvenance ?? (this.benchmark && this.assetStore instanceof assetstore.LocalJsonlProvider
? new assetstore.ProvenanceStore(this.assetStore.baseDir) : undefined);
this.sourceReview = deps.sourceReview ?? (this.benchmark && this.assetStore instanceof assetstore.LocalJsonlProvider
? new assetstore.ReviewLedger(this.assetStore.baseDir) : undefined);
const referenceBase = assetStoreDir ?? deps.assetStore?.baseDir;
this.referenceStore = referenceBase ? new reference.ReferenceStore(join(referenceBase, 'references')) : undefined;
this.referenceStore?.search({ max_assets: 1 });
this.atp = deps.atp;
const hubToUse = shadow ? shadow_.shadowHubCapability(deps.hub, deps.shadowSink, 'shadow') : deps.hub;
this.hub = hubToUse;
const hubBindings = hubNs.makeHubBindings(hubToUse, {
referenceScope: this.referenceStore?.scope,
...(deps.publishSanitizeEnv ? { sanitize: { env: deps.publishSanitizeEnv } } : {}),
});
this.proxyHandler = hubBindings.asProxyHandler();
const proxyHandler = this.proxyHandler;
const syncProxyHandler = (envelope) => this.handleHubModeBoundOutbound(envelope);
const assetByIdSource = isAssetByIdFetcher(deps.hub) ? deps.hub : (isAssetByIdFetcher(hubToUse) ? hubToUse : undefined);
this.remoteAssetById = assetByIdSource
? async (assetId) => {
const fetched = await assetByIdSource.fetchAssetById(assetId);
return assetMatchesId(fetched, assetId) ? fetched : null;
}
: undefined;
const publishRecallConfig = resolvePublishRecallConfig();
this.publishRecallVerifier = deps.publishRecallVerifier ?? new PublishRecallVerifier({
store: this.store,
...(!shadow && assetByIdSource
? { fetchAssetById: (assetId) => assetByIdSource.fetchAssetById(assetId) }
: {}),
config: shadow ? { ...publishRecallConfig, enabled: false } : publishRecallConfig,
now: this.now,
random: this.random,
stateKey: `publish_recall_verifier:${deps.runtimeNamespace ?? 'default'}:v1`,
});
this.reuseResultReporter = isReuseResultReporter(hubToUse)
? hubToUse
: (!shadow && isReuseResultReporter(deps.hub) ? deps.hub : undefined);
// validate() makes a LIVE POST /a2a/validate to the real hub (content-safety scan over the asset
// bundle) — it is the dry-run for a publish that shadow suppresses, so it is disabled under shadow
// like recordReuseResult and degrades to { valid:false, reason:'validate_not_configured' }.
this.validator = isValidator(hubToUse)
? hubToUse
: (!shadow && isValidator(deps.hub) ? deps.hub : undefined);
// proxy handler 装进 Dispatcher 仅供完整性; daemon 只 pump core, 实际出站走 SyncEngine.
this.dispatcher = new mailbox.Dispatcher({
store: this.store,
handlers: { core: (e) => this.handleCore(e), proxy: proxyHandler, agent: () => ({}) },
now: this.now,
});
this.daemon = new mailbox.MailboxDaemon({
store: this.store, dispatcher: this.dispatcher, now: this.now,
pumpHandlers: ['core'], // proxy 出站归 SyncEngine, 不在此双 claim
...(deps.lockPath ? { lockPath: deps.lockPath } : {}),
});
this.collaborationFacade = new CollaborationFacade({
store: this.store,
hub: hubToUse,
now: this.now,
notifyOutbound: () => this.notifyNewOutbound(),
...(deps.runtimeNamespace ? { runtimeNamespace: deps.runtimeNamespace } : {}),
...(deps.collaborationOperationTimeoutMs !== undefined ? { operationTimeoutMs: deps.collaborationOperationTimeoutMs } : {}),
});
this.sync = new SyncEngine({
store: this.store, hub: hubToUse, proxyHandler: syncProxyHandler, now: this.now,
...(this.referenceStore ? { referenceStore: this.referenceStore } : {}),
...(deps.runtimeNamespace ? { runtimeNamespace: deps.runtimeNamespace } : {}),
onOutboundSucceeded: (envelope, result) => {
this.collaborationFacade.handleOutboundSucceeded(envelope, result);
let shouldObserve = true;
try {
const cached = this.cacheSynchronousAssetSubmitSuccess(envelope, result);
if (cached === false)
shouldObserve = false;
}
catch { /* best-effort */ }
// Observability must never turn a Hub-accepted publish into a failed/retried economic action.
if (shouldObserve) {
try {
this.publishRecallVerifier.observeAcceptedPublish(envelope, result);
}
catch { /* best-effort */ }
}
// Recipe is the preferred public artifact. Failure here must not retry the already-accepted asset publish.
this.composeRecipeAfterAcceptedSubmit(envelope);
},
onOutboundTerminal: (envelope, error) => {
this.collaborationFacade.handleOutboundTerminal(envelope, error);
try {
this.cacheSynchronousAssetSubmitTerminal(envelope, error);
}
catch { /* best-effort */ }
},
acceptedOutcomeKey: (envelope) => !shadow && isSynchronousAssetSubmitEnvelope(envelope)
? synchronousAssetSubmitAcceptanceKey(envelope.idempotencyKey)
: undefined,
terminalOutcome: (envelope, error) => {
if (shadow || !isSynchronousAssetSubmitEnvelope(envelope))
return undefined;
const failure = mapSynchronousPublishFailure(error);
return {
key: synchronousAssetSubmitOutcomeKey(envelope.idempotencyKey),
result: { kind: 'failed', ...failure },
};
},
normalizeInboundEnvelope: (envelope) => this.collaborationFacade.normalizeInboundEnvelope(envelope),
...(deps.traceBackfill ? { onOutboundFlushed: () => { this.drainProxyTraceBackfill(); } } : {}),
});
this.lifecycle = new LifecycleManager({
store: this.store, auth: hubToUse.auth, hello: deps.hello, heartbeat: deps.heartbeat, now: this.now,
...(deps.heartbeatIntervalMs !== undefined ? { heartbeatIntervalMs: deps.heartbeatIntervalMs } : {}),
...(deps.evolverVersion ? { evolverVersion: deps.evolverVersion } : {}),
...(deps.helloMode ? { helloMode: deps.helloMode } : {}),
onForceUpdateDirective: (directive, source) => { this.triggerForceUpdateFromHeartbeat(directive, source); },
});
this.nextHeartbeatAt = this.now();
}
/**
* core handler(确定性, 不经 agent): 目前只接 force_update(#108). 其他 core 类型(asset_publish_result/
* feature_flag_update)仍由 Material/上层处理, 这里 no-op 标完成. force_update 仅当装配了 selfUpdate 才执行;
* 否则只标完成(不下载/不重启 — 默认 OFF 风险闸). 永不抛: 失败转结构化 telemetry, daemon 续跑旧版本.
*/
async handleCore(e) {
if (e.type !== 'force_update')
return {};
if (!this.deps.selfUpdate)
return { ok: false, reason: 'self_update_not_configured' };
const directive = (e.payload ?? {});
return this.executeAndReportForceUpdate(directive);
}
recordTickError(phase, err) {
const authLike = isAuthLikeError(err);
// #314: surface the actionable hint on an auth failure, keyed off the hub error code so it is not silent and
// not misdirected (a2a_auth_required => private hub; other auth error => credential problem).
const hint = authLike ? hubAuthFailureHint(process.env, errorMessage(err)) : '';
const message = safeDaemonMessage(`${phase}_tick: ${errorMessage(err)}${hint ? `. ${hint}` : ''}`, MAX_PROXY_TICK_ERROR_LENGTH);
try {
this.store.setState('sync:last_error', message);
if (authLike)
this.store.setState('hub:auth_status', 'auth_failed');
}
catch {
// Telemetry write failures must not break tick phase isolation.
}
return message;
}
/** 启动: 锁 + IPC 监听 + 初次 hello. 返回 IPC 端口. */
async start() {
if (this.started)
throw new Error('ProxyDaemon 已启动');
if (this.publishAbortController.signal.aborted)
this.publishAbortController = new AbortController();
try {
this.daemon.start();
this.ipc = new mailbox.MailboxIpcServer({
store: this.store, token: this.deps.ipcToken,
runtimeNamespace: this.deps.runtimeNamespace ?? 'default',
...(this.deps.ipcHost ? { host: this.deps.ipcHost } : {}), now: this.now,
onSend: (env, result) => {
if (result.stored && env.handler === 'proxy')
this.notifyNewOutbound();
},
...(this.deps.onIpcAuthFailure ? { onAuthFailure: this.deps.onIpcAuthFailure } : {}),
extraRoutes: [(ctx) => reference.withReferenceScope(this.referenceStore?.scope, () => this.handleProxyRoute(ctx))],
});
const port = await this.listenIpc(this.ipc);
try {
this.deps.onIpcListen?.(port);
}
catch { /* local discovery publishing must not block daemon startup */ }
await this.lifecycle.doHello();
this.lifecycleArmed = true;
this.drainProxyTraceBackfill();
try {
this.publishRecallVerifier.start();
}
catch { /* verifier availability must not block proxy startup */ }
this.started = true;
return port;
}
catch (err) {
await this.closeIpcBestEffort();
try {
await this.daemon.stop();
}
catch { /* best-effort cleanup */ }
try {
this.closeStoreOnce();
}
catch { /* best-effort cleanup */ }
this.started = false;
this.lifecycleArmed = false;
throw err;
}
}
async listenIpc(ipc) {
const requestedPort = this.deps.ipcPort ?? DEFAULT_IPC_PORT;
if (requestedPort !== 0)
return ipc.listen(requestedPort);
for (let attempt = 0; attempt < MAX_EPHEMERAL_IPC_LISTEN_ATTEMPTS; attempt += 1) {
const assignedPort = await ipc.listen(0);
if (!util.isFetchForbiddenPort(assignedPort))
return assignedPort;
// The outer start() cleanup owns the final listener when retries are exhausted.
if (attempt === MAX_EPHEMERAL_IPC_LISTEN_ATTEMPTS - 1) {
throw new Error('proxy_ipc_safe_port_unavailable');
}
await ipc.close();
}
throw new Error('proxy_ipc_safe_port_unavailable');
}
/** 单轮: core pump/TTL/wake + proxy 出站 + hub 入站 + 到点心跳. */
async tick() {
const errors = [];
const finalErrors = [];
const addFailure = (phase, message) => {
errors.push({ phase, message });
finalErrors.push(message);
};
try {
await this.daemon.tick(); // core + TTL + wake
}
catch (err) {
addFailure('core', this.recordTickError('core', err));
}
let outbound = emptyOutboundResult();
let heartbeat;
try {
outbound = await this.sync.syncOutbound();
if (outbound.authFailed) {
// #314: carry the engine's specific auth error (e.g. a2a_auth_required) up here so the operator-visible
// sync:last_error shows the real code + the correctly-keyed hint, not a generic "auth_failure".
const detail = outbound.authErrorMessage ?? 'auth_failure';
const authHint = hubAuthFailureHint(process.env, detail);
const outboundError = safeDaemonMessage(`outbound_tick: ${detail}${authHint ? `. ${authHint}` : ''}`, MAX_PROXY_TICK_ERROR_LENGTH);
try {
const generation = this.heartbeatGeneration;
const reauthed = await this.lifecycle.reauthenticate();
if (reauthed) {
heartbeat = { ok: false, reauthed: true };
this.recordHeartbeatResult(heartbeat, generation);
}
else {
addFailure('outbound', outboundError);
}
}
catch (reauthErr) {
addFailure('outbound', outboundError);
addFailure('heartbeat', this.recordTickError('heartbeat', reauthErr));
}
}
}
catch (err) {
addFailure('outbound', this.recordTickError('outbound', err));
}
let inbound = emptyInboundResult();
try {
inbound = await this.sync.syncInbound();
}
catch (err) {
const inboundError = this.recordTickError('inbound', err);
if (isAuthLikeError(err)) {
try {
const generation = this.heartbeatGeneration;
const reauthed = await this.lifecycle.reauthenticate();
if (reauthed) {
heartbeat = { ok: false, reauthed: true };
this.recordHeartbeatResult(heartbeat, generation);
}
else {
addFailure('inbound', inboundError);
}
}
catch (reauthErr) {
addFailure('inbound', inboundError);
addFailure('heartbeat', this.recordTickError('heartbeat', reauthErr));
}
}
else {
addFailure('inbound', inboundError);
}
}
if (!heartbeat && this.now() >= this.nextHeartbeatAt) {
const generation = this.heartbeatGeneration;
try {
heartbeat = await this.lifecycle.doHeartbeat();
}
catch (err) {
const message = safeHeartbeatTickErrorMessage(err);
heartbeat = { ok: false, reauthed: false, error: message };
this.recordHeartbeatTickException(message);
addFailure('heartbeat', `heartbeat_tick_exception:${message}`);
}
this.recordHeartbeatResult(heartbeat, generation);
}
if (finalErrors.length > 0) {
try {
this.store.setState('sync:last_error', safeDaemonMessage(finalErrors.join('; '), MAX_PROXY_TICK_ERROR_LENGTH));
}
catch { /* ignore telemetry persistence failures */ }
}
const failedPhases = uniqueTickPhases(errors.map((err) => err.phase));
const fatalCandidate = errors.length > 0 && isFatalTickCandidate(outbound, inbound, failedPhases);
this.lastTickAt = this.now();
this.consecutiveTickFailures = fatalCandidate ? this.consecutiveTickFailures + 1 : 0;
return {
outbound,
inbound,
...(heartbeat ? { heartbeat } : {}),
...(errors.length > 0 ? { errors, failedPhases, fatalCandidate } : { failedPhases: [], fatalCandidate: false }),
};
}
/** 下一轮建议延时: inbound 背压/idle 与 outbound pending cadence 取更快者. */
nextDelay(last) {
const inbound = this.sync.nextInboundDelay(last);
const outbound = this.sync.nextOutboundDelay();
const hubDirected = last.hasMore || last.nextPollAfterMs !== undefined;
const syncDelay = outbound !== SYNC_INTERVALS.outboundPending || hubDirected
? inbound
: Math.min(inbound, outbound);
const heartbeatDelay = Math.max(0, this.nextHeartbeatAt - this.now());
return Math.min(syncDelay, heartbeatDelay);
}
setWakeHandler(wake) {
this.loopWakeHandler = wake;
}
setExpectedNextTick(delayMs) {
this.nextTickDueAt = delayMs === undefined
? undefined
: this.now() + Math.max(0, delayMs);
}
notifyNewOutbound() {
if (this.loopWakeHandler) {
this.loopWakeHandler();
return;
}
this.wakeRunner();
}
/**
* Expedite the next heartbeat: clear the failure backoff, mark the heartbeat due now, and wake an
* in-flight runner sleep() so the next tick runs immediately. Wake-on-event for the pull-based
* loop — the interruptible sleep() lets this preempt a long backoff wait the way V1's timer-driven
* loop did (which armed a 0ms timer). The generation bump prevents a tick that was already in
* flight from overwriting this reschedule. No-op until the daemon is started.
*/
pokeHeartbeatLoop() {
if (!this.started)
return;
this.heartbeatGeneration += 1;
this.heartbeatFailures = 0;
this.nextHeartbeatAt = this.now();
this.wakeRunner();
}
/**
* Interruptible delay for the resident runner loop (bin/evolver-proxy.ts): resolves after `ms`, OR
* immediately when pokeHeartbeatLoop() fires while sleeping. A poke that lands between ticks (before
* the next sleep starts) sets wakeRunnerPending so the wake is not lost. The timer is unref'd so it
* never keeps the process alive on its own.
*/
async sleep(ms) {
if (this.wakeRunnerPending) {
this.wakeRunnerPending = false;
return;
}
return new Promise((resolve) => {
const timer = setTimeout(() => {
this.wakeRunnerResolve = undefined;
resolve();
}, ms);
if (typeof timer.unref === 'function')
timer.unref();
this.wakeRunnerResolve = () => {
clearTimeout(timer);
this.wakeRunnerResolve = undefined;
resolve();
};
});
}
wakeRunner() {
if (this.wakeRunnerResolve)
this.wakeRunnerResolve();
else
this.wakeRunnerPending = true;
}
health() {
return {
running: this.started,
ipcListening: !!this.ipc,
lifecycleArmed: this.lifecycleArmed,
...(this.lifecycle.nodeId ? { nodeId: this.lifecycle.nodeId } : {}),
lastWriteAt: this.daemon.lastWriteAt(),
...(this.lastTickAt !== undefined ? { lastTickAt: this.lastTickAt } : {}),
...(this.nextTickDueAt !== undefined ? { nextTickDueAt: this.nextTickDueAt } : {}),
consecutiveFailures: this.consecutiveTickFailures,
};
}
async stop() {
this.started = false;
this.lifecycleArmed = false;
if (!this.publishAbortController.signal.aborted) {
this.publishAbortController.abort(new Error('proxy_daemon_stopped'));
}
this.nextTickDueAt = undefined;
if (this.forceUpdateTimer) {
clearTimeout(this.forceUpdateTimer);
this.forceUpdateTimer = undefined;
this.scheduledForceUpdateKey = undefined;
}
// Release a runner blocked on sleep() so shutdown doesn't wait out a long delay.
this.wakeRunnerPending = false;
if (this.wakeRunnerResolve)
this.wakeRunnerResolve();
try {
await this.publishRecallVerifier.stop();
}
catch { /* best-effort verifier shutdown */ }
let stopError;
try {
await this.closeIpc();
}
catch (err) {
stopError = stopError ?? err;
}
try {
await this.daemon.stop();
}
catch (err) {
stopError = stopError ?? err;
}
try {
this.closeStoreOnce();
}
catch (err) {
stopError = stopError ?? err;
}
if (stopError)
throw stopError;
}
async closeIpc() {
const ipc = this.ipc;
this.ipc = undefined;
if (!ipc)
return;
await ipc.close();
}
async closeIpcBestEffort() {
try {
await this.closeIpc();
}
catch { /* best-effort cleanup */ }
}
closeStoreOnce() {
if (this.storeClosed)
return;
this.store.close();
this.storeClosed = true;
}
drainProxyTraceBackfill() {
const cfg = this.deps.traceBackfill;
const empty = {
scanned: 0,
queued: 0,
duplicates: 0,
skipped: 0,
files: 0,
reasons: {},
};
if (!cfg)
return empty;
if (this.traceBackfillDraining)
return { ...empty, deferred: true };
this.traceBackfillDraining = true;
try {
const stats = backfillProxyTraceUploads({
dir: cfg.dir,
store: this.store,
...(cfg.env ? { env: cfg.env } : {}),
now: this.now,
...(this.deps.runtimeNamespace ? { runtimeNamespace: this.deps.runtimeNamespace } : {}),
});
if (stats.queued > 0)
this.store.setState('llm_trace_backfill:last_queued', String(stats.queued));
return stats;
}
catch (err) {
this.store.setState('llm_trace_backfill:last_error', safeDaemonErrorMessage(err, MAX_PROXY_TICK_ERROR_LENGTH));
return { ...empty, skipped: 1, reasons: { thrown: 1 } };
}
finally {
this.traceBackfillDraining = false;
}
}
recordHeartbeatResult(result, generation) {
// A poke (pokeHeartbeatLoop) bumps heartbeatGeneration; a tick that began under an older
// generation has been superseded and must touch neither the failure count nor the schedule.
if (generation !== this.heartbeatGeneration)
return;
// V1 parity (lifecycle/manager.js _consecutiveFailures): every heartbeat failure backs off, not
// only thrown ones. The adapter maps non-auth 4xx (400/429/...) and unknown_node to a structured
// { ok:false } result with NO `error` field; the previous `!result.error` predicate left
// heartbeatFailures at 0 and pinned the cadence at the base interval forever — worst for 429,
// where the hub is rate-limiting and we kept hammering. A successful reauth (reauthed) is a
// recovery and resets, matching V1's 'recovered' branch. Hub-unreachable failures are diverted
// before this point and own their hubUnreachableUntil backoff, which nextHeartbeatDelay()
// prioritizes, so counting them here is harmless.
if (result.ok || result.reauthed) {
this.heartbeatFailures = 0;
}
else {
this.heartbeatFailures += 1;
}
this.nextHeartbeatAt = this.now() + this.lifecycle.nextHeartbeatDelay(this.heartbeatFailures);
}
recordHeartbeatTickException(message) {
try {
this.store.setState('sync:last_error', safeDaemonMessage(`heartbeat_tick_exception:${message}`, MAX_HEARTBEAT_TICK_ERROR_LENGTH));
}
catch {
// The daemon loop must keep running even if telemetry persistence is broken.
}
}
async executeAndReportForceUpdate(directive) {
if (!this.deps.selfUpdate)
return { ok: false, reason: 'self_update_not_configured' };
const currentVersion = this.deps.selfUpdate.currentVersion ?? this.deps.evolverVersion ?? '0.0.0';
const originalTelemetry = this.deps.selfUpdate.onTelemetry;
const originalCleanupWarning = this.deps.selfUpdate.onCleanupWarning;
const selfUpdateDeps = {
...this.deps.selfUpdate,
currentVersion,
onTelemetry: (result) => {
reportSelfUpdateLastUpdate(this.store, directive, result, {
fromVersion: currentVersion,
now: this.now(),
});
originalTelemetry?.(result);
},
onCleanupWarning: (warning, result) => {
try {
this.store.setState('self_update:last_cleanup_warning', safeDaemonMessage(`self_update_cleanup_warning:${warning}`, MAX_PROXY_TICK_ERROR_LENGTH));
}
catch {
// The returned result still carries cleanupWarning when operator state is unavailable.
}
originalCleanupWarning?.(warning, result);
},
};
return executeForceUpdate(directive, selfUpdateDeps);
}
triggerForceUpdateFromHeartbeat(directive, source) {
if (!this.deps.selfUpdate)
return;
const now = this.now();
const cooldownMs = forceUpdateRetryCooldownMs(process.env);
const key = forceUpdateDirectiveKey(directive);
if (this.forceUpdateTriggerInFlight) {
if (key !== this.forceUpdateLastTriggeredKey)
this.pendingForceUpdateDirective = { directive, source };
return;
}
if (this.scheduledForceUpdateKey) {
if (source === 'heartbeat_426') {
if (this.forceUpdateTimer)
clearTimeout(this.forceUpdateTimer);
this.forceUpdateTimer = undefined;
this.scheduledForceUpdateKey = undefined;
}
else {
if (key !== this.scheduledForceUpdateKey)
this.pendingForceUpdateDirective = { directive, source };
return;
}
}
if (source !== 'heartbeat_426'
&& this.forceUpdateLastTriggeredAt !== undefined
&& this.forceUpdateLastTriggeredKey === key
&& now - this.forceUpdateLastTriggeredAt < cooldownMs)
return;
const delayMs = source === 'heartbeat_200' ? forceUpdateScheduleDelayMs(directive, this.random) : 0;
if (delayMs > 0) {
this.forceUpdateLastTriggeredAt = now;
this.forceUpdateLastTriggeredKey = key;
this.scheduledForceUpdateKey = key;
this.reportPendingForceUpdate(directive);
this.forceUpdateTimer = setTimeout(() => {
this.forceUpdateTimer = undefined;
this.scheduledForceUpdateKey = undefined;
this.startForceUpdateExecution(directive, key);
}, delayMs);
return;
}
this.startForceUpdateExecution(directive, key);
}
startForceUpdateExecution(directive, key) {
this.forceUpdateTriggerInFlight = true;
this.forceUpdateLastTriggeredAt = this.now();
this.forceUpdateLastTriggeredKey = key;
void this.executeAndReportForceUpdate(directive).finally(() => {
this.forceUpdateTriggerInFlight = false;
const pending = this.pendingForceUpdateDirective;
this.pendingForceUpdateDirective = undefined;
if (pending)
this.triggerForceUpdateFromHeartbeat(pending.directive, pending.source);
});
}
reportPendingForceUpdate(directive) {
if (!this.deps.selfUpdate)
return;
const currentVersion = this.deps.selfUpdate.currentVersion ?? this.deps.evolverVersion ?? '0.0.0';
reportPendingSelfUpdateLastUpdate(this.store, directive, {
fromVersion: currentVersion,
now: this.now(),
});
}
stateNumber(key) {
const raw = this.store.getState(key);
if (!raw)
return null;
const n = Number(raw);
return Number.isFinite(n) && n > 0 ? n : null;
}
hubAuthFailed() {
return this.store.getState(HUB_AUTH_STATUS_STATE_KEY) === HUB_AUTH_FAILED;
}
markHubAuthFailed(error) {
const detail = safeDaemonErrorMessage(error, MAX_PROXY_TICK_ERROR_LENGTH);
const message = safeDaemonMessage(`hub_auth_failed: ${detail}`, MAX_PROXY_TICK_ERROR_LENGTH);
try {
this.store.setState(HUB_AUTH_STATUS_STATE_KEY, HUB_AUTH_FAILED);
this.store.setState('sync:last_error', message);
}
catch {
// A telemetry write failure must not weaken the request-level fail-closed response.
}
}
hubAuthFailureBody(extra = {}) {
return {
error: 'hub_credential_revoked_or_invalid',
auth_status: HUB_AUTH_FAILED,
local_fallback: false,
retryable: false,
operator_action: 'reauthenticate_hub',
...extra,
};
}
async localSearchAssets(query) {
const limit = Math.max(1, Math.min(Number(query.limit ?? 5), 25));
const local = this.assetStore ? await this.assetStore.search(query) : [];
return local.filter((asset) => asset.type !== 'AntiGene').slice(0, limit);
}
qualifyAssets(assets, limit = assets.length) {
if (!this.benchmark)
return { assets: assets.slice(0, limit) };
// 放在缓存、远端await之后;普通审批或cache TTL不能覆盖刚撤销的来源资格。
const snapshot = this.sourceProvenance?.snapshot();
const reviewed = this.sourceReview?.snapshot();
const sourceQualifications = assets.map((asset) => {
const decision = assetstore.assessSourceEligibility(asset, snapshot?.get(asset.asset_id), this.benchmark);
const review = reviewed?.get(asset.asset_id);
const withheld = snapshot?.get(asset.asset_id)?.trusted === false ? 'trust_rejected'
: (review && review.state !== 'approved') || (reviewed && asset.type === 'AntiGene' && !review) ? 'review_rejected'
: decision.allowed && !reviewed ? 'review_unavailable' : undefined;
return { ...decision, ...(withheld ? { allowed: false, reason: withheld } : {}),
assetId: asset.asset_id, benchmarkId: this.benchmark.benchmarkId };
});
return { assets: assets.filter((_asset, index) => sourceQualifications[index].allowed).slice(0, limit), sourceQualifications };
}
respondAssetSearch(ctx, query, assets, limit, extra = {}) {
const qualified = this.qualifyAssets(assets, limit);
ctx.json(200, { results: qualified.assets, ...qualified, query, ...extra, ...(this.benchmark ? { benchmarkId: this.benchmark.benchmarkId } : {}) });
}
qualifyFetch(ids, assets, missing) {
if (!this.benchmark)
return { assets: [...assets], missing: [...missing] };
const qualified = this.qualifyAssets(assets);
return { ...qualified, missing: ids.filter((id) => !qualified.assets.some((asset) => assetMatchesId(asset, id))) };
}
async localFetchAssets(ids) {
const assets = [];
const missing = [];
for (const id of ids) {
const local = this.assetStore ? await this.assetStore.get(id) : null;
if (assetMatchesId(local, id))
assets.push(local);
else
missing.push(id);
}
return this.qualifyFetch(ids, assets, missing);
}
async handleProxyRoute(ctx) {
const expectedHeader = singleHeader(ctx.req.headers['x-evomap-expected-hub-mode']);
if (hubModeMismatch(expectedHeader, this.deps.hubMode)) {
ctx.json(409, { error: 'proxy_hub_mode_mismatch' });
return true;
}
const handledAtp = await this.handleAtpRoute(ctx);
if (handledAtp)
return true;
if (ctx.route === 'GET /proxy/status') {
const publishReceiptDiagnostics = safePublishReceiptDiagnostics(this.hub);
ctx.json(200, {
running: true,
status: 'running',
proxy_protocol_version: PROXY_PROTOCOL_VERSION,
...(this.benchmark ? { benchmark_id: this.benchmark.benchmarkId, source_qualification_mode: 'enforce' } : {}),
schema_version: PROXY_STATUS_SCHEMA_VERSION,
hub_mode: this.deps.hubMode ?? 'public',
runtime_namespace: this.deps.runtimeNamespace ?? 'default',
node_id: this.lifecycle.nodeId ?? null,
outbound_pending: this.store.countPending('proxy', this.deps.runtimeNamespace),
inbound_pending: this.store.countPending('agent', this.deps.runtimeNamespace) + this.store.countPending('core', this.deps.runtimeNamespace),
last_sync_at: this.store.getState('sync:last_sync_at') ?? null,
last_sync_error: this.store.getState('sync:last_error') || null,
hub_auth_status: this.store.getState('hub:auth_status') || null,
hub_auth_failure_policy: this.hubAuthFailurePolicy,
reauth_backoff_until: this.stateNumber('lifecycle:reauth_until'),
hello_rate_limit_until: this.stateNumber('lifecycle:hello_rl_until'),
publish_recall_verify: this.publishRecallVerifier.status(),
...(publishReceiptDiagnostics ? { publish_receipt_diagnostics: publishReceiptDiagnostics } : {}),
});
return true;
}
if (ctx.route === 'POST /mailbox/poll') {
const body = asRecord(await ctx.readJson());
const limit = boundedRequestLimit(body['limit'], 10, 50);
if (limit === undefined) {
ctx.json(400, { error: 'invalid_limit' });
return true;
}
const channel = typeof body['channel'] === 'string' ? body['channel'] : undefined;
const type = typeof body['type'] === 'string' && body['type'] ? body['type'] : undefined;
const runtimeNamespace = legacyMailboxRuntimeNamespace(channel, this.deps.runtimeNamespace ?? 'default');
const messages = runtimeNamespace === undefined
? []
: this.store.list({
status: 'pending',
direction: mailboxDirection(body['direction']) ?? 'inbound',
runtimeNamespace,
...(type ? { type } : {}),
limit,
}).map(mailbox.legacyMailboxMessage);
ctx.json(200, { messages, count: messages.length });
return true;
}
if (await this.collaborationFacade.handle(ctx))
return true;
if (['POST /reference/import', 'POST /reference/search', 'POST /reference/fetch', 'POST /reference/context'].includes(ctx.route)) {
const body = asRecord(await ctx.readJson());
if (hubModeMismatch(body['expected_hub_mode'], this.deps.hubMode)) {
ctx.json(409, { error: 'proxy_hub_mode_mismatch' });
return true;
}
if (!this.referenceStore) {
ctx.json(503, { error: 'reference_store_not_configured' });
return true;
}
try {
if (ctx.route === 'POST /reference/import')
ctx.json(200, this.referenceStore.import(body['batch']));
else {
const query = reference.normalizeReferenceQuery(asRecord(body['query']));
if (ctx.route === 'POST /reference/search')
ctx.json(200, this.referenceStore.search(query));
else if (ctx.route === 'POST /reference/context')
ctx.json(200, this.referenceStore.context(query, body['max_chars'] === undefined ? undefined : Number(body['max_chars'])));
else {
if (this.hubAuthFailed()) {
ctx.json(401, this.hubAuthFailureBody());
return true;
}
if (!this.hub.references) {
ctx.json(501, { error: 'reference_hub_unsupported' });
return true;
}
const page = reference.decodeReferencePage(await this.hub.references.fetch(query), true);
const receipt = page.results.length ? this.referenceStore.import(page.results, 'hub') : { status: 'stored_reference', stored: 0, duplicates: 0, asset_ids: [], executable: false };
ctx.json(200, { ...receipt, ...page });
}
}
}
catch (error) {
if (isAuthLikeError(error)) {
this.markHubAuthFailed(error);
ctx.json(401, this.hubAuthFailureBody());
}
else
ctx.json(400, { error: safeDaemonErrorMessage(error, MAX_PROXY_TICK_ERROR_LENGTH) });
}
return true;
}
if (ctx.route === 'POST /asset/search') {
const body = (await ctx.readJson());
if (reference.hasEvidenceMode(body)) {
ctx.json(400, { error: 'use_reference_endpoint' });
return true;
}
if (hubModeMismatch(body.expected_hub_mode, this.deps.hubMode)) {
ctx.json(409, { error: 'proxy_hub_mode_mismatch' });
return true;
}
const limit = boundedRequestLimit(body.limit, 5, 25);
if (limit === undefined) {
ctx.json(400, { error: 'invalid_limit' });
return true;
}
const rawSignals = Array.isArray(body.signals) ? body.signals : body.signalsAny;
const signalsAny = Array.isArray(rawSignals) ? rawSignals.filter((s) => typeof s === 'string') : undefined;
const kind = assetKind(body.kind);
const query = {
...(signalsAny && signalsAny.length > 0 ? { signalsAny } : {}),
...(typeof body.text === 'string' ? { text: body.text } : {}),
...(kind ? { kind } : {}),
...(typeof body.category === 'string' ? { category: body.category } : {}),
...(typeof body.gene === 'string' ? { gene: body.gene } : {}),
limit: this.benchmark ? 25 : limit,
};
if (kind === 'AntiGene' && !this.assetStore) {
this.respondAssetSearch(ctx, body, [], limit);
return true;
}
if (kind === 'AntiGene') {
const results = this.assetStore ? await this.assetStore.search(query) : [];
this.respondAssetSearch(ctx, body, results, limit);
return true;
}
if (this.hubAuthFailed()) {
if (this.hubAuthFailurePolicy === 'deny') {
ctx.json(401, this.hubAuthFailureBody());
return true;
}
const results = await this.localSearchAssets(query);
this.respondAssetSearch(ctx, body, results, limit, {
degraded: true,
local_fallback: true,
auth_status: HUB_AUTH_FAILED,
warning: HUB_AUTH_FAILURE_WARNING,
});
return true;
}
try {
const results = await this.searchAssets(query);
this.respondAssetSearch(ctx, body, results, limit);
}
catch (error) {
if (!isAuthLikeError(error))
throw error;
this.markHubAuthFailed(error);
if (this.hubAuthFailurePolicy === 'deny') {
ctx.json(401, this.hubAuthFailureBody());
return true;
}
const results = await this.localSearchAssets(query);
this.respondAssetSearch(ctx, body, results, limit, {
degraded: true,
local_fallback: true,
auth_status: HUB_AUTH_FAILED,
warning: HUB_AUTH_FAILURE_WARNING,
});
}
return true;
}
if (ctx.route === 'POST /recipe/search') {
const body = (await ctx.readJson());
if (hubModeMismatch(body.expected_hub_mode, this.deps.hubMode)) {
ctx.json(409, { error: 'proxy_hub_mode_mismatch' });
return true;
}
const recipes = this.hub.recipes;
if (!recipes) {
ctx.json(501, { error: 'recipe_unsupported' });
return true;
}
const limit = boundedRequestLimit(body.limit, 10, 50);
if (limit === undefined) {
ctx.json(400, { error: 'invalid_limit' });
return true;
}
const q = [body.q, body.query, body.text].find((value) => typeof value === 'string' && value.trim().length > 0);
const cursor = typeof body.cursor === 'string' && body.cursor.trim() ? body.cursor.trim() : undefined;
const sort = typeof body.sort === 'string' && body.sort.trim() ? body.sort.trim() : undefined;
const request = {
...(q ? { q } : {}),
limit,
...(cursor ? { cursor } : {}),
...(sort ? { sort } : {}),
};
const receipt = q ? await recipes.search(request) : await recipes.list(request);
ctx.json(200, {
recipes: receipt.recipes,
...(receipt.nextCursor ? { nextCursor: receipt.nextCursor } : {}),
...(receipt.hasMore !== undefined ? { hasMore: receipt.hasMore } : {}),
query: body,
});
return true;
}
if (ctx.route === 'POST /recipe/express') {
const body = asRecord(await ctx.readJson());
if (hubModeMismatch(body['expected_hub_mode'], this.deps.hubMode)) {
ctx.json(409, { error: 'proxy_hub_mode_mismatch' });
return true;
}
const recipes = this.hub.recipes;
if (!recipes) {
ctx.json(501, { error: 'recipe_unsupported' });
return true;
}
const recipeId = typeof body['recipe_id'] === 'string'
? body['recipe_id']
: typeof body['recipeId'] === 'string'
? body['recipeId']
: '';
if (!recipeId.trim()) {
ctx.json(400, { error: 'recipe_id_required' });
return true;
}
const inputPayload = asRecord(body['input_payload']) ?? asRecord(body['inputPayload']) ?? {};
const receipt = await recipes.express(recipeId.trim(), { inputPayload });
ctx.json(200, receipt);
return true;
}
if (ctx.route === 'POST /asset/fetch') {
const body = (await ctx.readJson());
if (reference.hasEvidenceMode(body)) {
ctx.json(400, { error: 'use_reference_endpoint' });
return true;
}
if (hubModeMismatch(body.expected_hub_mode, this.deps.hubMode)) {
ctx.json(409, { error: 'proxy_hub_mode_mismatch' });
return true;
}
const ids = uniqueStrings([
...(Array.isArray(body.asset_ids) ? body.asset_ids : []),
...(typeof body.asset_id === 'string' ? [body.asset_id] : []),
]);
if (this.benchmark && ids.length > 25) {
ctx.json(400, { error: 'invalid_limit' });
return true;
}
if (this.hubAuthFailed()) {
if (this.hubAuthFailurePolicy === 'deny') {
ctx.json(401, this.hubAuthFailureBody());
return true;
}
const local = await this.localFetchAssets(ids);
ctx.json(200, {
...local,
query: body,
degraded: true,
local_fallback: true,
auth_status: HUB_AUTH_FAILED,
warning: HUB_AUTH_FAILURE_WARNING,
});
return true;
}
const assets = [];
const missing = [];
for (const id of ids) {
let got = null;
if (this.assetStore) {
got = await this.assetStore.get(id);
}
if (!got && this.remoteAssetById) {
try {
got = await this.remoteAssetById(id);
}
catch (error) {
if (!isAuthLikeError(error))
throw error;
this.markHubAuthFailed(error);
if (this.hubAuthFailurePolicy === 'deny') {
ctx.json(401, this.hubAuthFailureBody());
return true;
}
const local = await this.localFetchAssets(ids);
ctx.json(200, {
...local,
query: body,
degraded: true,
local_fallback: true,
auth_status: HUB_AUTH_FAILED,
warning: HUB_AUTH_FAILURE_WARNING,
});
return true;
}
}
if (assetMatchesId(got, id)) {
assets.push(got);
}
else {
missing.push(id);
}
}
ctx.json(200, { ...this.qualifyFetch(ids, assets, missing), query: body });
return true;
}
if (ctx.route === 'POST /asset/submit') {
const body = asRecord(await ctx.readJson());
if (hubModeMismatch(body['expected_hub_mode'], this.deps.hubMode)) {
ctx.json(409, { stored: false, error: 'proxy_hub_mode_mismatch' });
return true;
}
const bundle = normalizeAssetSubmitBundle(body);
const legacyAssetId = typeof body['asset_id'] === 'string' && body['asset_id'].trim()
? body['asset_id'].trim()
: undefined;
if (!bundle && !legacyAssetId) {
ctx.json(400, { error: 'assets or asset_id is required' });
return true;
}
let outboundBundle = bundle;
if (!outboundBundle && legacyAssetId) {
if (!this.assetStore) {
ctx.json(503, { error: 'asset_store_unavailable' });
return true;
}
const resolved = await this.assetStore.get(legacyAssetId);
if (!resolved) {
ctx.json(404, { error: 'asset_not_found', asset_id: legacyAssetId });
return true;
}
outboundBundle = [resolved];
}
if (outboundBundle && outboundBundle.length > MAX_ASSET_SUBMIT_ITEMS) {
ctx.json(400, { error: `asset submit accepts at most ${MAX_ASSET_SUBMIT_ITEMS} items` });
return true;
}
const requestedMode = ctx.url.searchParams.get('mode');
const mode = requestedMode ?? (bundle ? 'sync' : 'async');
if (mode !== 'sync' && mode !== 'async') {
ctx.json(400, { error: 'mode must be sync or async' });
return true;
}
if (this.hubAuthFailed()) {
ctx.json(401, this.hubAuthFailureBody({ publish_status: 'failed', queued: false, stored: false }));
return true;
}
if (mode === 'sync') {
if (!bundle) {
ctx.json(400, { error: 'mode=sync requires a full asset bundle' });
return true;
}
await this.publishAssetSubmitSynchronously(ctx, outboundBundle, hubNs.recipeComposeRequested(body));
return true;
}
const payload = {
assets: outboundBundle,
compose_recipe: hubNs.recipeComposeRequested(body),
[OUTBOUND_HUB_MODE_FIELD]: this.currentHubMode(),
};
const requestId = typeof body['request_id'] === 'string' && ASYNC_ASSET_SUBMIT_REQUEST_ID.test(body['request_id'])
? body['request_id']
: undefined;
delete payload['request_id'];
const runtimeNamespace = this.deps.runtimeNamespace ?? 'default';
const stableId = requestId ? asyncAssetSubmitEnvelopeId(runtimeNamespace, requestId) : undefined;
const env = mailbox.createEnvelope({
...(stableId ? { id: stableId, idempotencyKey: stableId } : {}),
type: 'asset_submit',
payload,
runtimeNamespace,
now: ctx.now,
});
const r = this.store.send(env);
if (r.stored)
this.notifyNewOutbound();
ctx.json(202, {
id: env.id,
message_id: env.id,
receiptId: r.receiptId,
status: 'pending',
publish_status: 'pending',
queued: true,
stored: r.stored,
});
return true;
}
if (ctx.route === 'POST /asset/validate') {
// Pre-publish dry-run against the hub's quality + content-safety gate (nothing stored, no credits).
// Same {assets:[…]} bundle shape as /asset/submit; the adapter wraps it in a GEP-A2A envelope.
const body = (await ctx.readJson());
if (hubModeMismatch(body.expected_hub_mode, this.deps.hubMode)) {
ctx.json(409, { valid: false, error: 'proxy_hub_mode_mismatch' });
return true;
}
const bundle = Array.isArray(body.assets)
? body.assets.filter((a) => Boolean(a && typeof a === 'object'))
: (body.asset && typeof body.asset === 'object' && !Array.isArray(body.asset) ? [body.asset] : []);
if (bundle.length === 0) {
ctx.json(400, { valid: false, error: 'assets or asset is required' });
return true;
}
const sanitized = hubNs.sanitizeBundle(bundle, { env: typeof process !== 'undefined' ? process.env : {} });
if (sanitized.blocked) {
ctx.json(200, { valid: false, reason: 'leak_blocked' });
return true;
}
if (!this.validator) {
ctx.json(200, { valid: false, reason: 'validate_not_configured' });
return true;
}
try {
ctx.json(200, await this.validator.validate(sanitized.bundle));
}
catch (e) {
ctx.json(200, { valid: false, reason: errorMessage(e) });
}
return true;
}
if (ctx.route === 'POST /asset/reuse-result') {
const body = await ctx.readJson();
if (hubModeMismatch(asRecord(body)['expected_hub_mode'], this.deps.hubMode)) {
ctx.json(409, { recorded: false, error: 'proxy_hub_mode_mismatch' });
return true;
}
if (!reference.isExecutionEligible(body, this.referenceStore?.scope)) {
ctx.json(200, { recorded: false, reason: 'reference_not_execution_evidence' });
return true;
}
const parsed = parseReuseResultReport(body);
if ('error' in parsed) {
ctx.json(400, { recorded: false, error: parsed.error });
return true;
}
if (!this.reuseResultReporter) {
ctx.json(200, { recorded: false, reason: 'reuse_result_not_configured' });
return true;
}
try {
ctx.json(200, await this.reuseResultReporter.recordReuseResult(parsed.report));
}
catch (e) {
ctx.json(200, { recorded: false, reason: safeDaemonErrorMessage(e, MAX_PROXY_TICK_ERROR_LENGTH) });
}
return true;
}
if (ctx.route === 'POST /conversation/distill') {
const body = (await ctx.readJson());
const publishRequested = body['publish'] === true;
const abortPublish = () => {
if (ctx.signal?.aborted)
return true;
if (!this.publishAbortController.signal.aborted)
return false;
// 守护进程停止时仍需结束开放的响应,否则 IPC server.close() 会等待该连接而无法完成停机。
if (!ctx.res.destroyed && !ctx.res.writableEnded) {
// 'blocked' (not 'failed'): the request was never queued, it was intercepted by
// shutdown; the 503 + error field already explains why.
ctx.json(503, { error: 'proxy_shutting_down', publish_status: 'blocked', queued: false });
}
return true;
};
if (hubModeMismatch(body.expected_hub_mode, this.deps.hubMode)) {
ctx.json(409, { error: 'proxy_hub_mode_mismatch' });
return true;
}
// 请求已取消时禁止继续生成或持久化发布草稿,避免把取消前的陈旧状态留下。
if (publishRequested && abortPublish())
return true;
const verifiedExecution = publishRequested
? await resolveVerifiedExecutionAfterPreflight(this.deps.publishExecutionVerifier, body, this.deps.publishExecutionVerifierTimeoutMs, [this.publishAbortController.signal, ...(ctx.signal ? [ctx.signal] : [])])
: undefined;
if (publishRequested && abortPublish())
return true;
const distill = await hubNs.distillConversation(body, {
// 调用方明确要求持久化时,即使发布被证据或质量闸门拦截,也保留可审查草稿;永不因此进入队列。
persist: body.persist === true,
store: this.assetStore,
...(verifiedExecution ? { verifiedExecution } : {}),
});
if (publishRequested && abortPublish())
return true;
if (!distill.ok) {
ctx.json(200, {
...distill,
queued: false,
submission: null,
publish_status: publishRequested ? 'blocked' : 'not_requested',
});
return true;
}
let submission = null;
if (publishRequested) {
if (abortPublish())
return true;
// 未达到可复用质量阈值的蒸馏结果仍可作为有用草稿,
// 但不得进入出站发布队列。安全与内容完整性闸门保持严格,质量负责晋级。
if (distill.publishable !== true) {
const reason = distill.quality.ok && verifiedExecution === undefined ? 'execution_evidence' : 'quality_gate';
ctx.json(200, {
...distill,
queued: false,
submission: null,
publish_blocked: reason,
publish_status: 'blocked',
});
return true;
}
// 已知 Hub 凭据撤销时不入队:与 /asset/submit 的撤销门同构,
// 避免 envelope 以不消耗 attempts 的 defer 无限期重试。
if (this.hubAuthFailed()) {
ctx.json(401, this.hubAuthFailureBody({ publish_status: 'failed', queued: false, stored: false }));
return true;
}
const env = mailbox.createEnvelope({
type: 'asset_submit',
payload: {
source: 'conversation_distillation',
distill_id: distill.distill_id,
assets: [distill.gene, distill.capsule],
compose_recipe: body['publish_recipe'] !== false,
title: typeof body.title === 'string' ? body.title : undefined,
description: typeof body.summary === 'string' ? body.summary : undefined,
[OUTBOUND_HUB_MODE_FIELD]: this.currentHubMode(),
},
runtimeNamespace: this.deps.runtimeNamespace ?? 'default',
now: ctx.now,
});
const r = this.store.send(env);
if (r.stored)
this.notifyNewOutbound();
submission = { id: env.id, message_id: env.id, receiptId: r.receiptId, status: 'pending', stored: r.stored };
}
ctx.json(200, {
...distill,
queued: submission !== null,
submission,
publish_status: publishRequested ? 'queued' : 'not_requested',
});
return true;
}
if (ctx.route === 'POST /agent/search') {
const body = asRecord(await ctx.readJson());
const directory = this.deps.hub.agentDirectory ?? hubNs.unsupportedAgentDirectoryCapability();
const parsed = parseAgentSearchRequest(body);
if (!parsed.ok) {
respondAgentDirectory(ctx, parsed);
return true;
}
const result = await directory.search(parsed.value);
respondAgentDirectory(ctx, result);
return true;
}
if (ctx.route === 'POST /agent/profile') {
const body = asRecord(await ctx.readJson());
const directory = this.deps.hub.agentDirectory ?? hubNs.unsupportedAgentDirectoryCapability();
let agentId;
let timeoutMs;
try {
agentId = hubNs.normalizeAgentId(typeof body['agent_id'] === 'string' ? body['agent_id'] : '');
timeoutMs = hubNs.normalizeAgentDirectoryTimeout(typeof body['timeout_ms'] === 'number' ? body['timeout_ms'] : undefined);
}
catch (error) {
respondAgentDirectory(ctx, invalidAgentDirectoryRequest(error));
return true;
}
const result = await directory.getProfile(agentId, { timeoutMs });
respondAgentDirectory(ctx, result);
return true;
}
if (ctx.route === 'POST /agent/discover') {
const body = asRecord(await ctx.readJson());
const directory = this.deps.hub.agentDirectory ?? hubNs.unsupportedAgentDirectoryCapability();
let request;
try {
request = hubNs.normalizeAgentTaskDiscoveryRequest({
title: typeof body['title'] === 'string' ? body['title'] : '',
...(typeof body['description'] === 'string' ? { description: body['description'] } : {}),
...(Array.isArray(body['signals']) ? { signals: body['signals'] } : {}),
...(typeof body['availability'] === 'string' ? { availability: body['availability'] } : {}),
...(typeof body['sort'] === 'string' ? { sort: body['sort'] } : {}),
...(typeof body['order'] === 'string' ? { order: body['order'] } : {}),
...(typeof body['cursor'] === 'string' ? { cursor: body['cursor'] } : {}),
...(typeof body['limit'] === 'number' ? { limit: body['limit'] } : {}),
...(typeof body['timeout_ms'] === 'number' ? { timeoutMs: body['timeout_ms'] } : {}),
});
}
catch (error) {
respondAgentDirectory(ctx, invalidAgentDirectoryRequest(error));
return true;
}
const result = await directory.discoverForTask(request);
respondAgentDirectory(ctx, result);
return true;
}
}
async searchAssets(query) {
const limit = Math.max(1, Math.min(Number(query.limit ?? 5), 25));
const local = this.assetStore ? await this.assetStore.search(query) : [];
if (query.kind === 'AntiGene')
return local.slice(0, limit);
const localSafe = local.filter((asset) => asset.type !== 'AntiGene');
let remote;
try {
remote = await this.searchRemoteAssets(query, limit);
}
catch (error) {
if (isAuthLikeError(error))
throw error;
if (localSafe.length === 0)
throw error;
remote = [];
}
reference.assertExecutionEligible(remote, this.referenceStore?.scope);
if (localSafe.length === 0)
return remote.slice(0, limit);
if (remote.length === 0)
return localSafe.slice(0, limit);
const seen = new Set();
const out = [];
// Keep proxy-backed PHub reuse visible even when the local asset cache has hits.
for (const asset of [...remote, ...localSafe]) {
if (seen.has(asset.asset_id))
continue;
seen.add(asset.asset_id);
out.push(asset);
if (out.length >= limit)
break;
}
return out;
}
async searchRemoteAssets(query, limit) {
const key = assetSearchCacheKey(this.deps.runtimeNamespace, query, limit);
const now = this.now();
const cached = this.assetSearchCache.get(key);
if (cached && cached.expiresAt > now)
return cached.value;
if (now < this.assetSearchCooldownUntil) {
if (cached && cached.staleUntil > now)
return cached.value;
if (cached)
this.assetSearchCache.delete(key);
throw new HubClientError(429, { error: 'rate_limited', source: 'asset_search_client_cooldown' }, this.assetSearchCooldownUntil - now);
}
const inflight = this.assetSearchInflight.get(key);
if (inflight)
return inflight;
const request = (async () => {
try {
const value = (await this.deps.hub.search(query))
.filter((asset) => asset.type !== 'AntiGene')
.slice(0, limit);
this.cacheRemoteAssetSearch(key, value, this.now());
return value;
}
catch (error) {
const retryAfterMs = assetSearchRetryAfterMs(error, this.assetSearchCacheTtlMs);
if (retryAfterMs !== undefined) {
const rateLimitedAt = this.now();
this.assetSearchCooldownUntil = Math.max(this.assetSearchCooldownUntil, rateLimitedAt + retryAfterMs);
const stale = this.assetSearchCache.get(key);
if (stale && stale.staleUntil > rateLimitedAt)
return stale.value;
}
throw error;
}
})();
this.assetSearchInflight.set(key, request);
const clearInflight = () => {
if (this.assetSearchInflight.get(key) === request)
this.assetSearchInflight.delete(key);
};
void request.then(clearInflight, clearInflight);
return request;
}
cacheRemoteAssetSearch(key, value, now) {
if (this.assetSearchCache.size >= this.assetSearchCacheMax && !this.assetSearchCache.has(key)) {
const oldest = this.assetSearchCache.keys().next().value;
if (oldest !== undefined)
this.assetSearchCache.delete(oldest);
}
this.assetSearchCache.delete(key);
this.assetSearchCache.set(key, {
value,
expiresAt: now + this.assetSearchCacheTtlMs,
staleUntil: now + this.assetSearchCacheTtlMs + this.assetSearchStaleGraceMs,
});
}
async publishAssetSubmitSynchronously(ctx, items, composeRecipe = true) {
const abortSynchronousPublish = () => {
if (ctx.signal?.aborted)
return true;
if (!this.publishAbortController.signal.aborted)
return false;
if (!ctx.res.destroyed && !ctx.res.writableEnded) {
ctx.json(503, { error: 'proxy_shutting_down', publish_status: 'failed', queued: false });
}
return true;
};
const classified = classifySynchronousAssetSubmit(items);
if (!classified.ok) {
ctx.json(422, { error: classified.error, code: 'invalid_asset_submit' });
return;
}
if (classified.kind === 'wire') {
if (abortSynchronousPublish())
return;
const envelope = this.createSynchronousAssetSubmitEnvelope(classified.bundle, undefined, ctx.now, composeRecipe);
this.writeSynchronousAssetSubmitOutcome(ctx, await this.publishSynchronousBundle(envelope));
return;
}
const results = [];
for (const item of classified.items) {
const converted = await convertLegacyLooseAsset(item, this.deps.publishExecutionVerifier, this.deps.publishExecutionVerifierTimeoutMs, ctx.signal, this.publishAbortController.signal);
if (!converted.ok) {
results.push({ ok: false, error: converted.error, statusCode: 422 });
continue;
}
if (abortSynchronousPublish())
return;
const envelope = this.createSynchronousAssetSubmitEnvelope(converted.bundle, 'v1_loose_asset_compat', ctx.now, composeRecipe);
const outcome = await this.publishSynchronousBundle(envelope);
if (outcome.kind === 'accepted') {
const receipt = outcome.receipt;
const publishedIds = submittedAssetIds(receipt, converted.bundle);
results.push({
ok: true,
gene_asset_id: publishedIds[0],
capsule_asset_id: publishedIds[1],
response: receipt,
});
}
else if (outcome.kind === 'failed') {
results.push({
ok: false,
error: String(outcome.body['error']),
statusCode: outcome.statusCode,
...(typeof outcome.body['reason'] === 'string' ? { reason: outcome.body['reason'] } : {}),
});
}
else {
results.push({
ok: false,
error: 'publish_pending',
statusCode: 202,
reason: `durable recovery pending (${outcome.messageId})`,
});
}
}
const summary = summarizeLegacySynchronousResults(results);
ctx.json(200, {
published: results.filter((result) => result.ok).length,
total: results.length,
results,
publish_status: summary.publishStatus,
queued: summary.queued,
});
}
createSynchronousAssetSubmitEnvelope(bundle, source, now, composeRecipe = true) {
const canonicalBundle = [...bundle].sort(compareSynchronousAssetSubmitAssets);
const runtimeNamespace = this.deps.runtimeNamespace ?? 'default';
const idempotencyKey = synchronousAssetSubmitKey(this.synchronousAssetSubmitScope, runtimeNamespace, this.currentHubMode(), canonicalBundle);
return mailbox.createEnvelope({
id: `compat:asset_submit:${idempotencyKey.slice(SYNC_ASSET_SUBMIT_PREFIX.length)}`,
type: 'asset_submit',
payload: {
...(source ? { source } : {}),
assets: canonicalBundle,
compose_recipe: composeRecipe,
[OUTBOUND_HUB_MODE_FIELD]: this.currentHubMode(),
},
idempotencyKey,
runtimeNamespace,
now,
});
}
composeRecipeAfterAcceptedSubmit(envelope) {
if (envelope.type !== 'asset_submit')
return;
const key = envelope.idempotencyKey || envelope.id;
if (this.recipeComposeStarted.has(key))
return;
this.recipeComposeStarted.add(key);
void hubNs.composeRecipeAfterAssetPublish(this.hub, asRecord(envelope.payload)).catch(() => { });
}
currentHubMode() {
return this.deps.hubMode ?? 'public';
}
handleHubModeBoundOutbound(envelope) {
if (envelope.type !== 'asset_submit')
return this.handleSynchronousProxyOutbound(envelope);
const payload = asRecord(envelope.payload);
const rawQueuedMode = payload[OUTBOUND_HUB_MODE_FIELD];
const queuedMode = rawQueuedMode === undefined ? 'public' : String(rawQueuedMode);
const currentMode = this.currentHubMode();
if ((queuedMode !== 'public' && queuedMode !== 'private') || queuedMode !== currentMode) {
throw new OutboundHubModeMismatchError(queuedMode, currentMode);
}
const outboundPayload = { ...payload };
delete outboundPayload[OUTBOUND_HUB_MODE_FIELD];
return this.handleSynchronousProxyOutbound({ ...envelope, payload: outboundPayload });
}
async publishSynchronousBundle(envelope) {
let cached = this.readSynchronousAssetSubmitOutcome(envelope);
if (cached?.kind === 'failed'
&& cached.body['error'] === 'hub_credential_revoked_or_invalid'
&& !this.hubAuthFailed()) {
this.store.deleteProcessed([
synchronousAssetSubmitOutcomeKey(envelope.idempotencyKey),
synchronousAssetSubmitAcceptanceKey(envelope.idempotencyKey),
]);
if (this.store.getStatus(envelope.id)?.dlq) {
this.store.replayDlq(envelope.id, this.now());
this.notifyNewOutbound();
}
cached = undefined;
}
if (cached)
return cached;
const inflight = this.synchronousAssetSubmitInflight.get(envelope.idempotencyKey);
if (inflight)
return this.waitForSynchronousAssetSubmit(inflight, envelope.id);
const { stored } = this.store.send(envelope);
const cachedAfterInsert = this.readSynchronousAssetSubmitOutcome(envelope);
if (cachedAfterInsert)
return cachedAfterInsert;
if (!stored) {
const existingInflight = this.synchronousAssetSubmitInflight.get(envelope.idempotencyKey);
return existingInflight
? this.waitForSynchronousAssetSubmit(existingInflight, envelope.id)
: { kind: 'pending', messageId: envelope.id };
}
this.store.defer(envelope.id, 'synchronous asset submit attempt in progress', this.now(), SYNC_ASSET_SUBMIT_DIRECT_RETRY_GRACE_MS);
this.notifyNewOutbound();
const request = this.executeSynchronousAssetSubmit(envelope);
this.synchronousAssetSubmitInflight.set(envelope.idempotencyKey, request);
void request.finally(() => {
if (this.synchronousAssetSubmitInflight.get(envelope.idempotencyKey) === request) {
this.synchronousAssetSubmitInflight.delete(envelope.idempotencyKey);
}
}).catch(() => { });
return this.waitForSynchronousAssetSubmit(request, envelope.id);
}
waitForSynchronousAssetSubmit(request, messageId) {
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
resolve({ kind: 'pending', messageId });
}, this.assetSubmitResponseTimeoutMs);
timeout.unref?.();
void request.then((outcome) => {
clearTimeout(timeout);
resolve(outcome);
}, (error) => {
clearTimeout(timeout);
reject(error);
});
});
}
async executeSynchronousAssetSubmit(envelope) {
try {
const receipt = await this.proxyHandler(envelope);
const firstObservation = this.cacheSynchronousAssetSubmitSuccess(envelope, receipt);
if (firstObservation) {
try {
this.publishRecallVerifier.observeAcceptedPublish(envelope, receipt);
}
catch { /* best-effort */ }
this.composeRecipeAfterAcceptedSubmit(envelope);
}
if (this.store.getById(envelope.id)?.status !== 'in_flight')
this.store.complete(envelope.id, this.now());
return { kind: 'accepted', receipt };
}
catch (error) {
if (isAuthLikeError(error))
this.markHubAuthFailed(error);
const current = this.store.getById(envelope.id);
const currentStatus = this.store.getStatus(envelope.id);
const cached = this.readSynchronousAssetSubmitOutcome(envelope);
// Once acceptance is durable, later publish or local-finalization errors cannot turn the external outcome
// into a failure. A late acceptance may also need to recover an intent that a racing rejection put in DLQ.
if (cached?.kind === 'accepted') {
if (currentStatus?.dlq) {
try {
this.store.replayDlq(envelope.id, this.now());
this.notifyNewOutbound();
}
catch (recoveryError) {
this.recordTickError('outbound', recoveryError);
}
}
return cached;
}
const failure = mapSynchronousPublishFailure(error);
const outcome = { kind: 'failed', ...failure, error };
if (this.shadowMode)
return outcome;
if (current?.status !== 'in_flight') {
const message = safeDaemonMessage(JSON.stringify(failure.body), MAX_PROXY_TICK_ERROR_LENGTH);
const outcomeKey = synchronousAssetSubmitOutcomeKey(envelope.idempotencyKey);
const acceptedKey = synchronousAssetSubmitAcceptanceKey(envelope.idempotencyKey);
const transitionNow = this.now();
const terminal = isTerminalSynchronousPublishFailure(error);
const transitioned = terminal
? this.store.failAndMarkProcessedUnlessProcessed(envelope.id, [acceptedKey], outcomeKey, { kind: 'failed', ...failure }, message, transitionNow, 1)
: isRetryableSynchronousPublishFailure(error)
? this.store.deferUnlessProcessed(envelope.id, acceptedKey, message, transitionNow, synchronousPublishRetryAfterMs(error, failure))
: this.store.failUnlessProcessed(envelope.id, acceptedKey, message, transitionNow);
if (!transitioned) {
const persisted = this.readSynchronousAssetSubmitOutcome(envelope);
if (persisted)
return persisted;
}
if (terminal) {
const persisted = this.readSynchronousAssetSubmitOutcome(envelope);
if (persisted?.kind === 'accepted')
return persisted;
}
this.notifyNewOutbound();
}
return outcome;
}
}
async handleSynchronousProxyOutbound(envelope) {
if (!isSynchronousAssetSubmitEnvelope(envelope))
return this.proxyHandler(envelope);
const cached = this.readSynchronousAssetSubmitOutcome(envelope);
if (cached?.kind === 'accepted')
return cached.receipt;
if (cached?.kind === 'failed')
throw cachedSynchronousAssetSubmitFailure(cached);
const inflight = this.synchronousAssetSubmitInflight.get(envelope.idempotencyKey);
if (inflight) {
const outcome = await this.waitForSynchronousAssetSubmit(inflight, envelope.id);
if (outcome.kind === 'accepted')
return outcome.receipt;
if (outcome.kind === 'failed')
throw outcome.error ?? cachedSynchronousAssetSubmitFailure(outcome);
if (this.synchronousAssetSubmitInflight.get(envelope.idempotencyKey) === inflight) {
this.synchronousAssetSubmitInflight.delete(envelope.idempotencyKey);
}
throw new HubUnreachableError('synchronous asset submit is still pending');
}
try {
const receipt = await this.proxyHandler(envelope);
const firstObservation = this.cacheSynchronousAssetSubmitSuccess(envelope, receipt);
if (firstObservation) {
try {
this.publishRecallVerifier.observeAcceptedPublish(envelope, receipt);
}
catch { /* best-effort */ }
this.composeRecipeAfterAcceptedSubmit(envelope);
}
return receipt;
}
catch (error) {
const cached = this.readSynchronousAssetSubmitOutcome(envelope);
if (cached?.kind === 'accepted')
return cached.receipt;
if (isTerminalSynchronousPublishFailure(error))
this.cacheSynchronousAssetSubmitTerminal(envelope, error);
throw error;
}
}
readSynchronousAssetSubmitOutcome(envelope) {
if (this.shadowMode || !isSynchronousAssetSubmitEnvelope(envelope))
return undefined;
const outcomeKey = synchronousAssetSubmitOutcomeKey(envelope.idempotencyKey);
const value = asRecord(this.store.getProcessed(outcomeKey));
if (value['kind'] === 'accepted' && Object.prototype.hasOwnProperty.call(value, 'receipt')) {
const receipt = asRecord(value['receipt']);
if (receipt['bundleId'] === 'shadow-bundle'
&& typeof receipt['receiptId'] === 'string'
&& receipt['receiptId'].startsWith('shadow-')) {
this.store.deleteProcessed([
outcomeKey,
synchronousAssetSubmitAcceptanceKey(envelope.idempotencyKey),
]);
return undefined;
}
const backfilled = this.store.markProcessedIf(outcomeKey, synchronousAssetSubmitAcceptanceKey(envelope.idempotencyKey), { accepted: true }, this.now(), (current) => {
const record = asRecord(current);
return record['kind'] === 'accepted' && Object.prototype.hasOwnProperty.call(record, 'receipt');
});
if (!backfilled)
return this.readSynchronousAssetSubmitOutcome(envelope);
return { kind: 'accepted', receipt: value['receipt'] };
}
if (value['kind'] === 'failed') {
const statusCode = positiveFiniteNumber(value['statusCode']);
if (statusCode !== undefined && isRecordValue(value['body'])) {
return { kind: 'failed', statusCode, body: value['body'] };
}
}
return undefined;
}
cacheSynchronousAssetSubmitSuccess(envelope, receipt) {
if (this.shadowMode || !isSynchronousAssetSubmitEnvelope(envelope))
return undefined;
const key = synchronousAssetSubmitOutcomeKey(envelope.idempotencyKey);
const acceptedKey = synchronousAssetSubmitAcceptanceKey(envelope.idempotencyKey);
const cached = this.readSynchronousAssetSubmitOutcome(envelope);
if (cached?.kind === 'accepted')
return false;
// Acceptance is monotonic: a concurrent attempt may reject after another request reached the Hub, but a
// real acceptance must supersede an earlier rejection so replay reflects the economic side effect.
this.store.replaceProcessedWithMarker(key, { kind: 'accepted', receipt }, acceptedKey, { accepted: true }, this.now());
return true;
}
cacheSynchronousAssetSubmitTerminal(envelope, error) {
if (this.shadowMode || !isSynchronousAssetSubmitEnvelope(envelope))
return;
if (this.store.isProcessed(synchronousAssetSubmitAcceptanceKey(envelope.idempotencyKey)))
return;
if (this.readSynchronousAssetSubmitOutcome(envelope)?.kind === 'accepted')
return;
const failure = mapSynchronousPublishFailure(error);
this.store.markProcessed(synchronousAssetSubmitOutcomeKey(envelope.idempotencyKey), {
kind: 'failed',
...failure,
}, this.now());
}
writeSynchronousAssetSubmitOutcome(ctx, outcome) {
if (outcome.kind === 'accepted') {
ctx.json(200, withSynchronousPublishStatus(outcome.receipt, 'accepted'));
}
else if (outcome.kind === 'failed') {
ctx.json(outcome.statusCode, withSynchronousPublishStatus(outcome.body, 'failed'));
}
else {
ctx.json(202, {
status: 'pending',
message_id: outcome.messageId,
durable: true,
publish_status: 'pending',
queued: true,
});
}
}
async handleAtpRoute(ctx) {
if (!ctx.url.pathname.startsWith('/atp/'))
return false;
if (!this.atp) {
ctx.json(503, { ok: false, error: 'atp_not_configured' });
return true;
}
const body = ctx.req.method === 'GET' ? {} : asRecord(await ctx.readJson());
if (ctx.route === 'POST /atp/order') {
const consent = this.deps.atpOrderConsent;
if (!consent) {
ctx.json(403, { ok: false, status: 403, error: 'atp_spend_consent_required', message: 'ATP order refused: spend consent gate is not configured' });
return true;
}
try {
consent.assertAllowed();
}
catch (err) {
ctx.json(403, {
ok: false,
status: 403,
error: 'atp_spend_consent_required',
message: err instanceof Error ? err.message : 'ATP order refused: auto-spend consent is disabled',
});
return true;
}
const capabilities = Array.isArray(body['capabilities']) ? body['capabilities'].filter((s) => typeof s === 'string') : [];
this.writeAtpJson(ctx, await this.atp.placeOrder({
capabilities,
budget: numberBody(body, 'budget'),
routingMode: stringBody(body, 'routingMode') ?? stringBody(body, 'routing_mode'),
verifyMode: stringBody(body, 'verifyMode') ?? stringBody(body, 'verify_mode'),
question: stringBody(body, 'question'),
signals: Array.isArray(body['signals']) ? body['signals'].filter((s) => typeof s === 'string') : undefined,
minReputation: numberBody(body, 'minReputation') ?? numberBody(body, 'min_reputation'),
}));
return true;
}
if (ctx.route === 'POST /atp/deliver') {
const orderId = stringBody(body, 'orderId') ?? stringBody(body, 'order_id') ?? '';
this.writeAtpJson(ctx, await this.atp.submitDelivery(orderId, body['proofPayload'] ?? body['proof_payload'] ?? {}));
return true;
}
if (ctx.route === 'POST /atp/verify') {
const orderId = stringBody(body, 'orderId') ?? stringBody(body, 'order_id') ?? '';
this.writeAtpJson(ctx, await this.atp.verifyDelivery(orderId, stringBody(body, 'action') ?? 'confirm'));
return true;
}
if (ctx.route === 'POST /atp/settle') {
const orderId = stringBody(body, 'orderId') ?? stringBody(body, 'order_id') ?? '';
this.writeAtpJson(ctx, await this.atp.settleOrder(orderId));
return true;
}
if (ctx.route === 'POST /atp/dispute') {
const orderId = stringBody(body, 'orderId') ?? stringBody(body, 'order_id') ?? '';
this.writeAtpJson(ctx, await this.atp.disputeOrder(orderId, stringBody(body, 'reason') ?? ''));
return true;
}
if (ctx.route === 'GET /atp/merchant/tier') {
this.writeAtpJson(ctx, await this.atp.getMerchantTier(ctx.url.searchParams.get('node_id') ?? undefined));
return true;
}
if (ctx.req.method === 'GET' && ctx.url.pathname.startsWith('/atp/order/')) {
const orderId = decodeURIComponent(ctx.url.pathname.slice('/atp/order/'.length));
this.writeAtpJson(ctx, await this.atp.getOrderStatus(orderId));
return true;
}
if (ctx.route === 'GET /atp/proofs') {
this.writeAtpJson(ctx, await this.atp.listProofs({
nodeId: this.lifecycle.nodeId,
role: ctx.url.searchParams.get('role') ?? undefined,
status: ctx.url.searchParams.get('status') ?? undefined,
limit: numberQuery(ctx.url, 'limit'),
}));
return true;
}
if (ctx.route === 'GET /atp/policy') {
this.writeAtpJson(ctx, await this.atp.getAtpPolicy());
return true;
}
ctx.json(404, { ok: false, error: 'unknown_atp_route' });
return true;
}
writeAtpJson(ctx, body) {
const rec = asRecord(body);
const status = rec['ok'] === false && typeof rec['status'] === 'number' ? rec['status'] : 200;
ctx.json(status, body);
}
}
function isAssetByIdFetcher(value) {
return Boolean(value && typeof value === 'object' && typeof value.fetchAssetById === 'function');
}
function isReuseResultReporter(value) {
return Boolean(value && typeof value === 'object' && typeof value.recordReuseResult === 'function');
}
function isValidator(value) {
return Boolean(value && typeof value === 'object' && typeof value.validate === 'function');
}
function assetMatchesId(asset, assetId) {
if (!reference.isExecutionEligible(asset) || !reference.isExecutionEligible(assetId))
return false;
if (!asset)
return false;
return assetId.startsWith('sha256:')
? asset.asset_id === assetId
: asset.asset_id === assetId || asset['id'] === assetId;
}
function hubModeMismatch(expected, actual) {
return expected !== undefined && expected !== (actual ?? 'public');
}
function singleHeader(value) {
return Array.isArray(value) ? value[0] : value;
}
function uniqueStrings(values) {
const seen = new Set();
const out = [];
for (const value of values) {
if (typeof value !== 'string' || value.length === 0 || seen.has(value))
continue;
seen.add(value);
out.push(value);
}
return out;
}
function assetSearchCacheKey(runtimeNamespace, query, limit) {
return JSON.stringify({
runtimeNamespace: runtimeNamespace ?? 'default',
kind: query.kind ?? null,
signalsAny: uniqueStrings(query.signalsAny ?? []).sort(),
category: query.category ?? null,
gene: query.gene ?? null,
text: query.text ?? null,
limit,
});
}
function assetSearchRetryAfterMs(error, fallbackMs) {
const structured = asRecord(error);
const details = asRecord(structured['details']);
const structuredStatus = structured['statusCode']
?? structured['status']
?? details['statusCode']
?? details['status'];
const status = error instanceof HubClientError
? error.status
: (typeof structuredStatus === 'number'
? structuredStatus
: (typeof structuredStatus === 'string' ? Number(structuredStatus) : NaN));
if (status !== 429)
return undefined;
const body = asRecord(error instanceof HubClientError ? error.body : structured['body']);
const retryAfterMs = positiveFiniteNumber(error instanceof HubClientError
? error.retryAfterMs
: structured['retryAfterMs'] ?? details['retryAfterMs']) ?? positiveFiniteNumber(body['retry_after_ms'] ?? body['retryAfterMs']);
const retryAfterSeconds = positiveFiniteNumber(body['retry_after'] ?? body['retryAfter']);
return Math.floor(Math.min(retryAfterMs ?? (retryAfterSeconds !== undefined ? retryAfterSeconds * 1_000 : fallbackMs), MAX_TIMER_DELAY_MS));
}
function positiveFiniteNumber(value) {
const parsed = typeof value === 'number'
? value
: (typeof value === 'string' && value.trim().length > 0 ? Number(value) : NaN);
return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;
}
function positiveIntegerOr(value, fallback) {
const parsed = positiveFiniteNumber(value);
return parsed === undefined ? fallback : Math.max(1, Math.floor(parsed));
}
function boundedRequestLimit(value, fallback, maximum) {
if (value === undefined)
return fallback;
if (typeof value !== 'number' || !Number.isSafeInteger(value) || value <= 0)
return undefined;
return Math.min(value, maximum);
}
function legacyMailboxRuntimeNamespace(requestedChannel, runtimeNamespace) {
if (requestedChannel === undefined || requestedChannel === 'evomap-hub' || requestedChannel === runtimeNamespace) {
return runtimeNamespace;
}
return undefined;
}
function mailboxDirection(value) {
return value === 'inbound' || value === 'outbound' || value === 'local' ? value : undefined;
}
function assetKind(value) {
return value === 'Gene' || value === 'Capsule' || value === 'EvolutionEvent' || value === 'AntiGene' ? value : undefined;
}
function asRecord(value) {
return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
}
function normalizeAssetSubmitBundle(body) {
if (Object.prototype.hasOwnProperty.call(body, 'assets')) {
const assets = body['assets'];
return Array.isArray(assets) && assets.length > 0 && assets.every(isNonEmptyAssetRecord)
? assets
: null;
}
return isNonEmptyAssetRecord(body['asset']) ? [body['asset']] : null;
}
function isNonEmptyAssetRecord(value) {
return Boolean(value && typeof value === 'object' && !Array.isArray(value) && Object.keys(value).length > 0);
}
function classifySynchronousAssetSubmit(items) {
const wireLooking = items.map(isWireLookingAsset);
const legacyLoose = items.map(isClearlyLegacyLooseAsset);
if (wireLooking.every(Boolean)) {
const bundle = [];
for (let index = 0; index < items.length; index += 1) {
const item = items[index];
if (!wire.validateWire(item).ok) {
return { ok: false, error: `asset ${index}: malformed V2 wire asset` };
}
try {
const normalized = assetstore.normalizeForPut(item);
if (!normalized.verified) {
return { ok: false, error: `asset ${index}: a verified content-addressed asset_id is required` };
}
bundle.push(normalized.record);
}
catch {
return { ok: false, error: `asset ${index}: asset_id does not match its content` };
}
}
return { ok: true, kind: 'wire', bundle };
}
if (legacyLoose.every(Boolean))
return { ok: true, kind: 'legacy', items };
if (wireLooking.some(Boolean) && legacyLoose.some(Boolean)) {
return { ok: false, error: 'wire assets and legacy loose assets cannot be mixed in one request' };
}
if (wireLooking.some(Boolean)) {
return { ok: false, error: 'all wire-looking items must be valid content-addressed V2 assets' };
}
return { ok: false, error: 'unsupported asset input; provide V2 wire assets or legacy content/summary/strategy' };
}
function isWireLookingAsset(value) {
return Object.prototype.hasOwnProperty.call(value, 'schema_version')
|| Object.prototype.hasOwnProperty.call(value, 'asset_id');
}
function isClearlyLegacyLooseAsset(value) {
if (Object.prototype.hasOwnProperty.call(value, 'schema_version')
|| Object.prototype.hasOwnProperty.call(value, 'asset_id'))
return false;
return ['content', 'summary', 'strategy'].some((key) => Object.prototype.hasOwnProperty.call(value, key));
}
async function resolveVerifiedExecution(verifier, input, expectedValidation, timeoutMs = DEFAULT_PUBLISH_EXECUTION_VERIFY_TIMEOUT_MS, parentSignals = []) {
if (!verifier)
return undefined;
if (parentSignals.some((signal) => signal.aborted))
return undefined;
const boundedTimeout = Number.isSafeInteger(timeoutMs) && timeoutMs > 0
? timeoutMs
: DEFAULT_PUBLISH_EXECUTION_VERIFY_TIMEOUT_MS;
const controller = new AbortController();
let timer;
let resolveAborted;
const aborted = new Promise((resolve) => {
resolveAborted = () => resolve(null);
});
const onParentAbort = () => {
if (!controller.signal.aborted)
controller.abort(new Error('publish_verification_aborted'));
resolveAborted();
};
for (const signal of parentSignals)
signal.addEventListener('abort', onParentAbort, { once: true });
try {
const timeout = new Promise((resolve) => {
timer = setTimeout(() => {
if (!controller.signal.aborted)
controller.abort(new Error('publish_verification_timeout'));
resolve(null);
}, boundedTimeout);
timer.unref?.();
});
const candidate = await Promise.race([verifier(input, controller.signal), timeout, aborted]);
if (!candidate || !Array.isArray(candidate.trace) || candidate.trace.length === 0)
return undefined;
if (controller.signal.aborted)
return undefined;
if (candidate.trace.some((row) => (!row
|| typeof row.command !== 'string'
|| row.command.trim().length === 0
|| !Number.isInteger(row.exit)
|| row.exit !== 0)))
return undefined;
if (candidate.trace.length !== expectedValidation.length)
return undefined;
if (candidate.trace.some((row, index) => row.command.trim() !== expectedValidation[index]))
return undefined;
// Compare the authoritative raw receipt first, then cross the common execution-evidence outlet exactly once.
// A command that required redaction no longer identifies the executed program and must not unlock publication.
const sanitized = verify.sanitizeExecutionPayload({ ...candidate, validation: expectedValidation });
return sanitized.blocked ? undefined : sanitized.value;
}
catch {
return undefined;
}
finally {
if (timer !== undefined)
clearTimeout(timer);
for (const signal of parentSignals)
signal.removeEventListener('abort', onParentAbort);
if (!controller.signal.aborted)
controller.abort(new Error('publish_verification_complete'));
}
}
function declaredValidationCommands(input) {
const raw = input.validation ?? input.verification ?? (input.execution && typeof input.execution === 'object' && !Array.isArray(input.execution)
? input.execution['validation']
: undefined);
if (!Array.isArray(raw) || raw.length === 0 || raw.length > 8)
return undefined;
const commands = raw.map((command) => typeof command === 'string' ? command.trim() : '');
if (commands.some((command) => command.length === 0
|| command.length > 180
|| !verify.isValidationCommandAllowed(command)
|| verify.sanitizeExecutionCommand(command).blocked)) {
return undefined;
}
return commands;
}
async function resolveVerifiedExecutionAfterPreflight(verifier, input, timeoutMs, parentSignals = []) {
if (!verifier)
return undefined;
if (parentSignals.some((signal) => signal.aborted))
return undefined;
const preflight = await hubNs.distillConversation(input, { persist: false });
if (parentSignals.some((signal) => signal.aborted) || !preflight.ok || !preflight.quality.ok)
return undefined;
const validation = declaredValidationCommands(input);
if (!validation)
return undefined;
// 只把通过质量与命令策略预检的最小验证输入交给宿主,绝不转发调用方的 execution/status/trace。
return resolveVerifiedExecution(verifier, { validation }, validation, timeoutMs, parentSignals);
}
async function convertLegacyLooseAsset(value, verifyExecution, verifyExecutionTimeoutMs, ...parentSignals) {
const normalized = legacyLooseDistillInput(value);
if (!normalized.ok)
return normalized;
try {
const verifiedExecution = await resolveVerifiedExecutionAfterPreflight(verifyExecution, normalized.input, verifyExecutionTimeoutMs, parentSignals.filter((signal) => signal !== undefined));
const distilled = await hubNs.distillConversation(normalized.input, {
persist: false,
...(verifiedExecution ? { verifiedExecution } : {}),
});
if (!distilled.ok)
return { ok: false, error: `legacy_distill_${safeIdentifier(distilled.reason)}` };
// Mirror the /conversation/distill route: a draft that passed the quality gate but was
// blocked only by the missing host execution evidence must not be reported as a
// quality-gate failure.
if (distilled.publishable !== true) {
return {
ok: false,
error: distilled.quality.ok && verifiedExecution === undefined
? 'legacy_distill_execution_evidence'
: 'legacy_distill_quality_gate',
};
}
const gene = {
...distilled.gene,
...(normalized.constraints
? { constraints: mergeLegacyConstraints(distilled.gene['constraints'], normalized.constraints) }
: {}),
...(normalized.category ? { category: normalized.category } : {}),
};
const bundle = [gene, distilled.capsule].map(deterministicDistilledAsset);
if (!bundle.every((asset) => wire.validateWire(asset).ok)) {
return { ok: false, error: 'legacy_distill_invalid_wire_output' };
}
return { ok: true, bundle };
}
catch {
return { ok: false, error: 'legacy_distill_failed' };
}
}
function legacyLooseDistillInput(value) {
const content = strictOptionalString(value, 'content');
const summary = strictOptionalString(value, 'summary');
if (!content.ok || !summary.ok)
return { ok: false, error: 'legacy content and summary must be strings' };
const strategy = strictOptionalStringList(value, 'strategy', 10, 220);
if (!strategy.ok)
return { ok: false, error: 'legacy strategy must be an array of strings' };
if (strategy.value && (strategy.value.length < 2 || strategy.value.some((step) => step.length < 15))) {
return { ok: false, error: 'legacy strategy requires at least two steps of 15 characters each' };
}
const text = [content.value, summary.value].filter(Boolean).join('\n').trim();
const suppliedSubstance = [text, ...(strategy.value ?? [])].join(' ').trim();
if (!strategy.value && text.length < 50) {
return { ok: false, error: 'legacy content or summary must contain at least 50 characters' };
}
if (suppliedSubstance.length < 50) {
return { ok: false, error: 'legacy input does not contain enough substantive content' };
}
const signals = strictOptionalStringList(value, 'signals', 12, 64);
const signalsMatch = strictOptionalStringList(value, 'signals_match', 12, 64);
const validation = strictOptionalStringList(value, 'validation', 8, 180);
const verification = strictOptionalStringList(value, 'verification', 8, 180);
const artifacts = strictOptionalStringList(value, 'artifacts', 12, 240);
if (!signals.ok || !signalsMatch.ok || !validation.ok || !verification.ok || !artifacts.ok) {
return { ok: false, error: 'legacy list fields must contain strings only' };
}
const constraints = parseLegacyConstraints(value['constraints']);
if (!constraints.ok)
return constraints;
const category = parseLegacyCategory(value['category']);
if (!category.ok)
return category;
const derivedSummary = summary.value
|| content.value?.slice(0, 300)
|| strategy.value?.join('; ').slice(0, 300)
|| '';
const input = {
summary: derivedSummary,
transcript: content.value ?? derivedSummary,
...(strategy.value ? { strategy: strategy.value } : {}),
...((signals.value ?? signalsMatch.value) ? { signals: signals.value ?? signalsMatch.value } : {}),
...((validation.value ?? verification.value) ? { validation: validation.value ?? verification.value } : {}),
...(artifacts.value ? { artifacts: artifacts.value } : {}),
...strictForwardString(value, 'title'),
...strictForwardString(value, 'name'),
...strictForwardString(value, 'platform'),
...strictForwardString(value, 'model'),
...strictForwardString(value, 'thread_id'),
...(isRecordValue(value['execution']) ? { execution: value['execution'] } : {}),
...(isRecordValue(value['blast_radius']) ? { blast_radius: value['blast_radius'] } : {}),
// Compatibility callers may not lower the V2 quality gate.
min_score: 5,
persist: false,
};
return {
ok: true,
input,
...(constraints.value ? { constraints: constraints.value } : {}),
...(category.value ? { category: category.value } : {}),
};
}
function parseLegacyConstraints(value) {
if (value === undefined)
return { ok: true };
if (!isRecordValue(value))
return { ok: false, error: 'legacy constraints must be an object' };
if (Object.keys(value).some((key) => key !== 'max_files' && key !== 'forbidden_paths')) {
return { ok: false, error: 'legacy constraints contains unsupported fields' };
}
const maxFiles = value['max_files'];
if (maxFiles !== undefined && (!Number.isInteger(maxFiles) || Number(maxFiles) < 1 || Number(maxFiles) > 10_000)) {
return { ok: false, error: 'legacy constraints.max_files must be an integer from 1 to 10000' };
}
const forbiddenPaths = value['forbidden_paths'];
if (forbiddenPaths !== undefined && (!Array.isArray(forbiddenPaths)
|| forbiddenPaths.length > 50
|| forbiddenPaths.some((path) => typeof path !== 'string' || path.trim().length === 0 || path.trim().length > 200))) {
return { ok: false, error: 'legacy constraints.forbidden_paths must be a bounded string array' };
}
const normalizedPaths = Array.isArray(forbiddenPaths)
? uniqueStrings(forbiddenPaths.map((path) => String(path).trim()))
: undefined;
return {
ok: true,
value: {
...(typeof maxFiles === 'number' ? { max_files: maxFiles } : {}),
...(normalizedPaths ? { forbidden_paths: normalizedPaths } : {}),
},
};
}
function mergeLegacyConstraints(base, legacy) {
const current = isRecordValue(base) ? base : {};
const currentMax = Number.isInteger(current['max_files']) && Number(current['max_files']) > 0
? Number(current['max_files'])
: 20;
const currentPaths = Array.isArray(current['forbidden_paths'])
? current['forbidden_paths'].filter((path) => typeof path === 'string')
: [];
return {
max_files: Math.min(currentMax, legacy.max_files ?? currentMax),
forbidden_paths: uniqueStrings([...currentPaths, ...(legacy.forbidden_paths ?? [])]),
};
}
function parseLegacyCategory(value) {
if (value === undefined)
return { ok: true };
if (value === 'repair' || value === 'optimize' || value === 'innovate' || value === 'explore') {
return { ok: true, value };
}
return { ok: false, error: 'legacy category is invalid' };
}
function deterministicDistilledAsset(asset) {
const draft = wire.stripGeneHints({ ...asset, asset_id: '' });
// `_source` is local distiller provenance and is not part of the current GEP Gene schema.
// It also contains a wall-clock timestamp, so it must not influence compatibility asset ids.
delete draft['_source'];
return assetstore.normalizeForPut(draft).record;
}
function strictOptionalString(value, key) {
if (!Object.prototype.hasOwnProperty.call(value, key))
return { ok: true };
const raw = value[key];
if (typeof raw !== 'string')
return { ok: false };
const trimmed = raw.trim();
return { ok: true, ...(trimmed ? { value: trimmed } : {}) };
}
function strictOptionalStringList(value, key, maxItems, maxLength) {
if (!Object.prototype.hasOwnProperty.call(value, key))
return { ok: true };
const raw = value[key];
if (!Array.isArray(raw) || raw.some((item) => typeof item !== 'string'))
return { ok: false };
const normalized = raw.map((item) => item.trim()).filter(Boolean).slice(0, maxItems);
if (normalized.some((item) => item.length > maxLength))
return { ok: false };
return { ok: true, ...(normalized.length > 0 ? { value: normalized } : {}) };
}
function strictForwardString(value, key) {
const parsed = strictOptionalString(value, key);
return parsed.ok && parsed.value ? { [key]: parsed.value } : {};
}
function safePublishReceiptDiagnostics(value) {
try {
if (!hubNs.isPublishDiagnosticsCapability(value))
return undefined;
const raw = value.publishDiagnostics();
if (!Number.isSafeInteger(raw.malformed2xxCount) || raw.malformed2xxCount < 0)
return undefined;
const lastMalformed2xxAt = raw.lastMalformed2xxAt;
const lastMalformed2xxStatus = raw.lastMalformed2xxStatus;
if (lastMalformed2xxAt !== undefined && (!Number.isSafeInteger(lastMalformed2xxAt) || lastMalformed2xxAt < 0))
return undefined;
if (lastMalformed2xxStatus !== undefined && (!Number.isInteger(lastMalformed2xxStatus) || lastMalformed2xxStatus < 200 || lastMalformed2xxStatus >= 300))
return undefined;
return {
malformed2xxCount: raw.malformed2xxCount,
...(lastMalformed2xxAt !== undefined ? { lastMalformed2xxAt } : {}),
...(lastMalformed2xxStatus !== undefined ? { lastMalformed2xxStatus } : {}),
};
}
catch {
return undefined;
}
}
function isRecordValue(value) {
return Boolean(value && typeof value === 'object' && !Array.isArray(value));
}
function safeIdentifier(value) {
return value.replace(/[^a-z0-9_]+/gi, '_').slice(0, 80) || 'rejected';
}
function synchronousAssetSubmitKey(scope, runtimeNamespace, hubMode, bundle) {
const assetIds = bundle.map((asset) => asset.asset_id).sort();
const digestInput = hubMode === 'private'
? [scope, runtimeNamespace, hubMode, assetIds]
: [scope, runtimeNamespace, assetIds];
const digest = createHash('sha256')
.update(JSON.stringify(digestInput))
.digest('hex');
return `${SYNC_ASSET_SUBMIT_PREFIX}${digest}`;
}
function asyncAssetSubmitEnvelopeId(runtimeNamespace, requestId) {
const digest = createHash('sha256')
.update(JSON.stringify([runtimeNamespace, requestId]))
.digest('hex');
return `${ASYNC_ASSET_SUBMIT_PREFIX}${digest}`;
}
function compareSynchronousAssetSubmitAssets(left, right) {
const typeOrder = SYNC_ASSET_SUBMIT_TYPE_RANK[left.type] - SYNC_ASSET_SUBMIT_TYPE_RANK[right.type];
if (typeOrder !== 0)
return typeOrder;
if (left.asset_id < right.asset_id)
return -1;
if (left.asset_id > right.asset_id)
return 1;
return 0;
}
function synchronousAssetSubmitOutcomeKey(idempotencyKey) {
return `${idempotencyKey}:outcome`;
}
function synchronousAssetSubmitAcceptanceKey(idempotencyKey) {
return `${idempotencyKey}:accepted`;
}
function cachedSynchronousAssetSubmitFailure(outcome) {
const retryAfterMs = positiveFiniteNumber(outcome.body['retry_after_ms']);
return new hubNs.PublishRejectedError(typeof outcome.body['status'] === 'string'
? outcome.body['status']
: String(outcome.body['error'] ?? 'rejected'), true, typeof outcome.body['reason'] === 'string' ? outcome.body['reason'] : undefined, retryAfterMs, false);
}
function isSynchronousAssetSubmitEnvelope(envelope) {
return envelope.type === 'asset_submit'
&& envelope.idempotencyKey.startsWith(SYNC_ASSET_SUBMIT_PREFIX);
}
function isTerminalSynchronousPublishFailure(error) {
if (error instanceof AuthError || errorName(error) === 'AuthError')
return true;
return error instanceof hubNs.PublishRejectedError && error.terminal;
}
function isRetryableSynchronousPublishFailure(error) {
if (error instanceof HubUnreachableError || errorName(error) === 'HubUnreachableError')
return true;
if (error instanceof hubNs.PublishRejectedError) {
return !error.terminal && (error.retryable === true || error.retryAfterMs !== undefined);
}
if (error instanceof HubClientError || errorName(error) === 'HubClientError') {
const status = error instanceof HubClientError ? error.status : Number(asRecord(error)['status']);
return status === 429 || (status >= 500 && status <= 599);
}
return false;
}
function synchronousPublishRetryAfterMs(error, failure) {
const fromBody = positiveFiniteNumber(failure.body['retry_after_ms']);
if (fromBody !== undefined)
return Math.max(1_000, fromBody);
if (error instanceof hubNs.PublishRejectedError && error.retryAfterMs !== undefined) {
return Math.max(1_000, error.retryAfterMs);
}
if (error instanceof HubUnreachableError)
return Math.max(1_000, error.retryAfterMs);
if (error instanceof HubClientError && error.retryAfterMs !== undefined) {
return Math.max(1_000, error.retryAfterMs);
}
return 60_000;
}
function submittedAssetIds(result, fallback) {
const record = asRecord(result);
for (const key of ['submittedAssetIds', 'assetIds']) {
const value = record[key];
if (Array.isArray(value) && value.every((item) => typeof item === 'string'))
return value;
}
return fallback.map((asset) => asset.asset_id);
}
function mapSynchronousPublishFailure(error) {
if (error instanceof hubNs.PublishRejectedError) {
const status = publishRejectionStatus(error.status);
if (status === 'cooldown') {
return {
statusCode: 429,
body: {
error: 'hub_rate_limited',
...(error.retryAfterMs !== undefined ? { retry_after_ms: error.retryAfterMs } : {}),
},
};
}
if (status === 'credit_shortage') {
return { statusCode: 402, body: { error: 'hub_payment_required' } };
}
return {
statusCode: error.terminal ? 422 : 503,
body: {
error: 'publish_rejected',
status,
terminal: error.terminal,
reason: status === 'leak_blocked'
? 'sensitive data detected before publish'
: 'Hub did not accept the publish',
...(error.retryAfterMs !== undefined ? { retry_after_ms: error.retryAfterMs } : {}),
},
};
}
if (error instanceof AuthError || errorName(error) === 'AuthError') {
return {
statusCode: 401,
body: {
error: 'hub_credential_revoked_or_invalid',
auth_status: HUB_AUTH_FAILED,
local_fallback: false,
retryable: false,
},
};
}
if (error instanceof HubUnreachableError || errorName(error) === 'HubUnreachableError') {
const retryAfterMs = error instanceof HubUnreachableError ? error.retryAfterMs : positiveFiniteNumber(asRecord(error)['retryAfterMs']);
return {
statusCode: 503,
body: { error: 'hub_unreachable', ...(retryAfterMs !== undefined ? { retry_after_ms: retryAfterMs } : {}) },
};
}
if (error instanceof HubClientError || errorName(error) === 'HubClientError') {
const status = error instanceof HubClientError ? error.status : Number(asRecord(error)['status']);
if (status === 429) {
const retryAfterMs = error instanceof HubClientError ? error.retryAfterMs : positiveFiniteNumber(asRecord(error)['retryAfterMs']);
return {
statusCode: 429,
body: { error: 'hub_rate_limited', ...(retryAfterMs !== undefined ? { retry_after_ms: retryAfterMs } : {}) },
};
}
if (status === 402)
return { statusCode: 402, body: { error: 'hub_payment_required' } };
return { statusCode: status >= 500 ? 503 : 502, body: { error: 'hub_publish_failed' } };
}
return { statusCode: 502, body: { error: 'hub_publish_failed' } };
}
function errorName(value) {
return typeof asRecord(value)['name'] === 'string' ? String(asRecord(value)['name']) : undefined;
}
function publishRejectionStatus(value) {
return value === 'quarantine'
|| value === 'leak_blocked'
|| value === 'cooldown'
|| value === 'credit_shortage'
? value
: 'rejected';
}
function respondAgentDirectory(ctx, result) {
if (result.ok) {
ctx.json(200, result);
return;
}
const status = {
invalid_request: 400,
permission_denied: 403,
capability_unavailable: 501,
invalid_response: 502,
hub_unavailable: 503,
timeout: 504,
}[result.error.code];
ctx.json(status, result);
}
function parseAgentSearchRequest(body) {
try {
return { ok: true, value: hubNs.normalizeAgentSearchRequest({
...(typeof body['query'] === 'string' ? { query: body['query'] } : {}),
...(Array.isArray(body['signals']) ? { signals: body['signals'] } : {}),
...(typeof body['availability'] === 'string' ? { availability: body['availability'] } : {}),
...(typeof body['sort'] === 'string' ? { sort: body['sort'] } : {}),
...(typeof body['order'] === 'string' ? { order: body['order'] } : {}),
...(typeof body['cursor'] === 'string' ? { cursor: body['cursor'] } : {}),
...(typeof body['limit'] === 'number' ? { limit: body['limit'] } : {}),
...(typeof body['timeout_ms'] === 'number' ? { timeoutMs: body['timeout_ms'] } : {}),
}) };
}
catch (error) {
return invalidAgentDirectoryRequest(error);
}
}
function invalidAgentDirectoryRequest(error) {
return {
ok: false,
error: {
code: 'invalid_request',
retryable: false,
message: error instanceof Error ? error.message.slice(0, 120) : 'invalid_request',
},
};
}
function stringBody(body, key) {
const v = body[key];
return typeof v === 'string' && v.length > 0 ? v : undefined;
}
function numberBody(body, key) {
const raw = body[key];
const n = typeof raw === 'number' ? raw : (typeof raw === 'string' ? Number(raw) : NaN);
return Number.isFinite(n) ? n : undefined;
}
function optionalNonNegativeNumberBody(body, keys, error) {
let value;
for (const key of keys) {
if (!Object.prototype.hasOwnProperty.call(body, key))
continue;
const raw = body[key];
const n = typeof raw === 'number' ? raw : (typeof raw === 'string' && raw.trim().length > 0 ? Number(raw) : NaN);
if (!Number.isFinite(n) || n < 0)
return { error };
value ??= n;
}
return { value };
}
const REUSE_RESULT_OUTCOMES = new Set(['success', 'failed', 'mismatched', 'stale', 'unsafe']);
function parseReuseResultReport(value) {
const body = asRecord(value);
const assetId = stringBody(body, 'assetId') ?? stringBody(body, 'asset_id');
if (!assetId)
return { error: 'asset_id_required' };
const outcome = stringBody(body, 'outcome');
if (!isReuseResultOutcome(outcome))
return { error: 'invalid_outcome' };
const taskId = stringBody(body, 'taskId') ?? stringBody(body, 'task_id');
const traceId = stringBody(body, 'traceId') ?? stringBody(body, 'trace_id');
const tokensSavedParsed = optionalNonNegativeNumberBody(body, ['tokensSaved', 'tokens_saved'], 'invalid_tokens_saved');
if ('error' in tokensSavedParsed)
return { error: tokensSavedParsed.error };
const timeSavedSecondsParsed = optionalNonNegativeNumberBody(body, ['timeSavedSeconds', 'time_saved_seconds'], 'invalid_time_saved_seconds');
if ('error' in timeSavedSecondsParsed)
return { error: timeSavedSecondsParsed.error };
const reason = stringBody(body, 'reason');
return {
report: {
assetId,
outcome,
...(taskId ? { taskId } : {}),
...(traceId ? { traceId } : {}),
...(timeSavedSecondsParsed.value !== undefined ? { timeSavedSeconds: timeSavedSecondsParsed.value } : {}),
...(reason ? { reason: reason.slice(0, 1000) } : {}),
},
};
}
function isReuseResultOutcome(value) {
return typeof value === 'string' && REUSE_RESULT_OUTCOMES.has(value);
}
function errorMessage(err) {
return err instanceof Error ? err.message : String(err);
}
function safeDaemonMessage(message, maxLength) {
try {
return hubNs.redactString(message).slice(0, maxLength);
}
catch {
return '[REDACTED]';
}
}
function safeDaemonErrorMessage(err, maxLength) {
return safeDaemonMessage(errorMessage(err), maxLength);
}
function isAuthLikeError(err) {
const name = err?.name;
return name === 'AuthError' || /\b(401|403|unauthorized|forbidden|auth)\b/i.test(errorMessage(err));
}
function emptyOutboundResult() {
return { sent: 0, failed: 0, terminal: 0, deferred: 0 };
}
function emptyInboundResult() {
return { received: 0, enqueued: 0, hasMore: false };
}
function uniqueTickPhases(phases) {
return Array.from(new Set(phases));
}
function isFatalTickCandidate(outbound, inbound, failedPhases) {
const failed = new Set(failedPhases);
return failed.has('core')
&& failed.has('outbound')
&& failed.has('inbound')
&& !hasTickSyncProgress(outbound, inbound);
}
function hasTickSyncProgress(outbound, inbound) {
return outbound.sent > 0
|| outbound.terminal > 0
|| inbound.received > 0
|| inbound.enqueued > 0
|| inbound.hasMore;
}
function safeHeartbeatTickErrorMessage(err) {
return safeDaemonErrorMessage(err, MAX_HEARTBEAT_TICK_ERROR_LENGTH);
}
function numberQuery(url, key) {
const raw = url.searchParams.get(key);
if (raw === null)
return undefined;
const n = Number(raw);
return Number.isFinite(n) ? n : undefined;
}
function forceUpdateRetryCooldownMs(env) {
const raw = env['EVOLVER_FORCE_UPDATE_RETRY_COOLDOWN_MS'];
if (raw === undefined || raw.trim() === '')
return 60_000;
const n = Number(raw);
return Number.isFinite(n) && n >= 0 ? n : 60_000;
}
function forceUpdateScheduleDelayMs(directive, random) {
const staggerWindowMs = nonNegativeFiniteNumber(directive.stagger_window_ms);
if (staggerWindowMs === undefined || staggerWindowMs <= 0)
return 0;
const deadlineMs = nonNegativeFiniteNumber(directive.deadline_ms);
const ratio = safeRandomRatio(random);
const sampledDelayMs = ratio * staggerWindowMs;
const maxDelayMs = Math.min(staggerWindowMs, deadlineMs ?? staggerWindowMs, MAX_TIMER_DELAY_MS);
return Math.max(0, Math.min(sampledDelayMs, maxDelayMs));
}
function nonNegativeFiniteNumber(value) {
return typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : undefined;
}
function safeRandomRatio(random) {
try {
return clampedRandom(random());
}
catch {
return 0;
}
}
function clampedRandom(value) {
if (typeof value !== 'number')
return 0;
if (!Number.isFinite(value))
return 0;
return Math.max(0, Math.min(value, 1));
}
function forceUpdateDirectiveKey(directive) {
const manifest = directive.manifest;
const manifestVersion = manifest && typeof manifest === 'object' && !Array.isArray(manifest)
? manifest.version
: undefined;
return [
directive.directive_id ?? '',
directive.required_version ?? '',
typeof manifestVersion === 'string' ? manifestVersion : '',
].join('\x1f');
}