Tighten bridge and teleport debug redaction
This commit is contained in:
@@ -55,6 +55,36 @@ export class BridgeFatalError extends Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function summarizeBridgeApiPayloadForDebug(data: unknown): string {
|
||||||
|
if (data === null) return 'null'
|
||||||
|
if (data === undefined) return 'undefined'
|
||||||
|
if (Array.isArray(data)) {
|
||||||
|
return debugBody({
|
||||||
|
type: 'array',
|
||||||
|
length: data.length,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (typeof data !== 'object') {
|
||||||
|
return String(data)
|
||||||
|
}
|
||||||
|
const value = data as Record<string, unknown>
|
||||||
|
const workData =
|
||||||
|
value.data && typeof value.data === 'object'
|
||||||
|
? (value.data as Record<string, unknown>)
|
||||||
|
: undefined
|
||||||
|
return debugBody({
|
||||||
|
type: 'object',
|
||||||
|
keys: Object.keys(value)
|
||||||
|
.sort()
|
||||||
|
.slice(0, 10),
|
||||||
|
hasEnvironmentId: typeof value.environment_id === 'string',
|
||||||
|
hasEnvironmentSecret: typeof value.environment_secret === 'string',
|
||||||
|
hasWorkId: typeof value.id === 'string',
|
||||||
|
workType: typeof workData?.type === 'string' ? workData.type : undefined,
|
||||||
|
hasSessionId: typeof workData?.id === 'string',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export function createBridgeApiClient(deps: BridgeApiDeps): BridgeApiClient {
|
export function createBridgeApiClient(deps: BridgeApiDeps): BridgeApiClient {
|
||||||
function debug(msg: string): void {
|
function debug(msg: string): void {
|
||||||
deps.onDebug?.(msg)
|
deps.onDebug?.(msg)
|
||||||
@@ -168,12 +198,14 @@ export function createBridgeApiClient(deps: BridgeApiDeps): BridgeApiClient {
|
|||||||
|
|
||||||
handleErrorStatus(response.status, response.data, 'Registration')
|
handleErrorStatus(response.status, response.data, 'Registration')
|
||||||
debug(
|
debug(
|
||||||
`[bridge:api] POST /v1/environments/bridge -> ${response.status} environment_id=${response.data.environment_id}`,
|
`[bridge:api] POST /v1/environments/bridge -> ${response.status}`,
|
||||||
)
|
)
|
||||||
debug(
|
debug(
|
||||||
`[bridge:api] >>> ${debugBody({ max_sessions: config.maxSessions, metadata: { worker_type: config.workerType } })}`,
|
`[bridge:api] >>> ${debugBody({ max_sessions: config.maxSessions, metadata: { worker_type: config.workerType } })}`,
|
||||||
)
|
)
|
||||||
debug(`[bridge:api] <<< ${debugBody(response.data)}`)
|
debug(
|
||||||
|
`[bridge:api] <<< ${summarizeBridgeApiPayloadForDebug(response.data)}`,
|
||||||
|
)
|
||||||
return response.data
|
return response.data
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -221,9 +253,11 @@ export function createBridgeApiClient(deps: BridgeApiDeps): BridgeApiClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
debug(
|
debug(
|
||||||
`[bridge:api] GET .../work/poll -> ${response.status} workId=${response.data.id} type=${response.data.data?.type}${response.data.data?.id ? ` sessionId=${response.data.data.id}` : ''}`,
|
`[bridge:api] GET .../work/poll -> ${response.status} type=${response.data.data?.type ?? 'unknown'}`,
|
||||||
|
)
|
||||||
|
debug(
|
||||||
|
`[bridge:api] <<< ${summarizeBridgeApiPayloadForDebug(response.data)}`,
|
||||||
)
|
)
|
||||||
debug(`[bridge:api] <<< ${debugBody(response.data)}`)
|
|
||||||
return response.data
|
return response.data
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -427,7 +461,9 @@ export function createBridgeApiClient(deps: BridgeApiDeps): BridgeApiClient {
|
|||||||
`[bridge:api] POST /v1/sessions/${sessionId}/events -> ${response.status}`,
|
`[bridge:api] POST /v1/sessions/${sessionId}/events -> ${response.status}`,
|
||||||
)
|
)
|
||||||
debug(`[bridge:api] >>> ${debugBody({ events: [event] })}`)
|
debug(`[bridge:api] >>> ${debugBody({ events: [event] })}`)
|
||||||
debug(`[bridge:api] <<< ${debugBody(response.data)}`)
|
debug(
|
||||||
|
`[bridge:api] <<< ${summarizeBridgeApiPayloadForDebug(response.data)}`,
|
||||||
|
)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { logForDebugging } from '../utils/debug.js'
|
import { logForDebugging } from '../utils/debug.js'
|
||||||
import { errorMessage } from '../utils/errors.js'
|
import { toError } from '../utils/errors.js'
|
||||||
import { jsonStringify } from '../utils/slowOperations.js'
|
import { jsonStringify } from '../utils/slowOperations.js'
|
||||||
import { extractErrorDetail } from './debugUtils.js'
|
import { extractErrorDetail } from './debugUtils.js'
|
||||||
|
|
||||||
@@ -23,6 +23,62 @@ function oauthHeaders(accessToken: string): Record<string, string> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function summarizeCodeSessionResponseForDebug(data: unknown): string {
|
||||||
|
if (data === null) return 'null'
|
||||||
|
if (data === undefined) return 'undefined'
|
||||||
|
if (Array.isArray(data)) {
|
||||||
|
return jsonStringify({
|
||||||
|
payloadType: 'array',
|
||||||
|
length: data.length,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (typeof data === 'object') {
|
||||||
|
const value = data as Record<string, unknown>
|
||||||
|
const session =
|
||||||
|
value.session && typeof value.session === 'object'
|
||||||
|
? (value.session as Record<string, unknown>)
|
||||||
|
: undefined
|
||||||
|
return jsonStringify({
|
||||||
|
payloadType: 'object',
|
||||||
|
keys: Object.keys(value)
|
||||||
|
.sort()
|
||||||
|
.slice(0, 10),
|
||||||
|
hasSession: Boolean(session),
|
||||||
|
hasSessionId: typeof session?.id === 'string',
|
||||||
|
hasWorkerJwt: typeof value.worker_jwt === 'string',
|
||||||
|
hasApiBaseUrl: typeof value.api_base_url === 'string',
|
||||||
|
hasExpiresIn: typeof value.expires_in === 'number',
|
||||||
|
hasWorkerEpoch:
|
||||||
|
typeof value.worker_epoch === 'number' ||
|
||||||
|
typeof value.worker_epoch === 'string',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return typeof data
|
||||||
|
}
|
||||||
|
|
||||||
|
function summarizeCodeSessionErrorForDebug(err: unknown): string {
|
||||||
|
const error = toError(err)
|
||||||
|
const summary: Record<string, unknown> = {
|
||||||
|
errorType: error.constructor.name,
|
||||||
|
errorName: error.name,
|
||||||
|
hasMessage: error.message.length > 0,
|
||||||
|
hasStack: Boolean(error.stack),
|
||||||
|
}
|
||||||
|
if (err && typeof err === 'object') {
|
||||||
|
const errorObj = err as Record<string, unknown>
|
||||||
|
if (typeof errorObj.code === 'string' || typeof errorObj.code === 'number') {
|
||||||
|
summary.code = errorObj.code
|
||||||
|
}
|
||||||
|
if (errorObj.response && typeof errorObj.response === 'object') {
|
||||||
|
const response = errorObj.response as Record<string, unknown>
|
||||||
|
if (typeof response.status === 'number') {
|
||||||
|
summary.httpStatus = response.status
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return jsonStringify(summary)
|
||||||
|
}
|
||||||
|
|
||||||
export async function createCodeSession(
|
export async function createCodeSession(
|
||||||
baseUrl: string,
|
baseUrl: string,
|
||||||
accessToken: string,
|
accessToken: string,
|
||||||
@@ -47,7 +103,9 @@ export async function createCodeSession(
|
|||||||
)
|
)
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
logForDebugging(
|
logForDebugging(
|
||||||
`[code-session] Session create request failed: ${errorMessage(err)}`,
|
`[code-session] Session create request failed: ${summarizeCodeSessionErrorForDebug(
|
||||||
|
err,
|
||||||
|
)}`,
|
||||||
)
|
)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@@ -72,7 +130,9 @@ export async function createCodeSession(
|
|||||||
!data.session.id.startsWith('cse_')
|
!data.session.id.startsWith('cse_')
|
||||||
) {
|
) {
|
||||||
logForDebugging(
|
logForDebugging(
|
||||||
`[code-session] No session.id (cse_*) in response: ${jsonStringify(data).slice(0, 200)}`,
|
`[code-session] No session.id (cse_*) in response: ${summarizeCodeSessionResponseForDebug(
|
||||||
|
data,
|
||||||
|
)}`,
|
||||||
)
|
)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@@ -110,7 +170,9 @@ export async function fetchRemoteCredentials(
|
|||||||
)
|
)
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
logForDebugging(
|
logForDebugging(
|
||||||
`[code-session] /bridge request failed: ${errorMessage(err)}`,
|
`[code-session] /bridge request failed: ${summarizeCodeSessionErrorForDebug(
|
||||||
|
err,
|
||||||
|
)}`,
|
||||||
)
|
)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@@ -136,7 +198,9 @@ export async function fetchRemoteCredentials(
|
|||||||
!('worker_epoch' in data)
|
!('worker_epoch' in data)
|
||||||
) {
|
) {
|
||||||
logForDebugging(
|
logForDebugging(
|
||||||
`[code-session] /bridge response malformed (need worker_jwt, expires_in, api_base_url, worker_epoch): ${jsonStringify(data).slice(0, 200)}`,
|
`[code-session] /bridge response malformed (need worker_jwt, expires_in, api_base_url, worker_epoch): ${summarizeCodeSessionResponseForDebug(
|
||||||
|
data,
|
||||||
|
)}`,
|
||||||
)
|
)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,15 +21,10 @@ const SECRET_PATTERN = new RegExp(
|
|||||||
'g',
|
'g',
|
||||||
)
|
)
|
||||||
|
|
||||||
const REDACT_MIN_LENGTH = 16
|
|
||||||
|
|
||||||
export function redactSecrets(s: string): string {
|
export function redactSecrets(s: string): string {
|
||||||
return s.replace(SECRET_PATTERN, (_match, field: string, value: string) => {
|
return s.replace(SECRET_PATTERN, (_match, field: string, value: string) => {
|
||||||
if (value.length < REDACT_MIN_LENGTH) {
|
void value
|
||||||
return `"${field}":"[REDACTED]"`
|
return `"${field}":"[REDACTED]"`
|
||||||
}
|
|
||||||
const redacted = `${value.slice(0, 8)}...${value.slice(-4)}`
|
|
||||||
return `"${field}":"${redacted}"`
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,6 +47,73 @@ export function debugBody(data: unknown): string {
|
|||||||
return s.slice(0, DEBUG_MSG_LIMIT) + `... (${s.length} chars)`
|
return s.slice(0, DEBUG_MSG_LIMIT) + `... (${s.length} chars)`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function summarizeValueShapeForDebug(value: unknown): unknown {
|
||||||
|
if (value === null) return 'null'
|
||||||
|
if (value === undefined) return 'undefined'
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return {
|
||||||
|
type: 'array',
|
||||||
|
length: value.length,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (typeof value === 'object') {
|
||||||
|
return {
|
||||||
|
type: 'object',
|
||||||
|
keys: Object.keys(value as Record<string, unknown>)
|
||||||
|
.sort()
|
||||||
|
.slice(0, 10),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return typeof value
|
||||||
|
}
|
||||||
|
|
||||||
|
export function summarizeBridgeErrorForDebug(err: unknown): string {
|
||||||
|
const summary: Record<string, unknown> = {}
|
||||||
|
|
||||||
|
if (err instanceof Error) {
|
||||||
|
summary.errorType = err.constructor.name
|
||||||
|
summary.errorName = err.name
|
||||||
|
summary.hasMessage = err.message.length > 0
|
||||||
|
summary.hasStack = Boolean(err.stack)
|
||||||
|
} else {
|
||||||
|
summary.errorType = typeof err
|
||||||
|
summary.hasValue = err !== undefined && err !== null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err && typeof err === 'object') {
|
||||||
|
const errorObj = err as Record<string, unknown>
|
||||||
|
if (
|
||||||
|
typeof errorObj.code === 'string' ||
|
||||||
|
typeof errorObj.code === 'number'
|
||||||
|
) {
|
||||||
|
summary.code = errorObj.code
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
typeof errorObj.errno === 'string' ||
|
||||||
|
typeof errorObj.errno === 'number'
|
||||||
|
) {
|
||||||
|
summary.errno = errorObj.errno
|
||||||
|
}
|
||||||
|
if (typeof errorObj.status === 'number') {
|
||||||
|
summary.status = errorObj.status
|
||||||
|
}
|
||||||
|
if (typeof errorObj.syscall === 'string') {
|
||||||
|
summary.syscall = errorObj.syscall
|
||||||
|
}
|
||||||
|
if (errorObj.response && typeof errorObj.response === 'object') {
|
||||||
|
const response = errorObj.response as Record<string, unknown>
|
||||||
|
if (typeof response.status === 'number') {
|
||||||
|
summary.httpStatus = response.status
|
||||||
|
}
|
||||||
|
if ('data' in response) {
|
||||||
|
summary.responseData = summarizeValueShapeForDebug(response.data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsonStringify(summary)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract a descriptive error message from an axios error (or any error).
|
* Extract a descriptive error message from an axios error (or any error).
|
||||||
* For HTTP errors, appends the server's response body message if available,
|
* For HTTP errors, appends the server's response body message if available,
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ export function createTokenRefreshScheduler({
|
|||||||
// (such as the follow-up refresh set by doRefresh) so the refresh
|
// (such as the follow-up refresh set by doRefresh) so the refresh
|
||||||
// chain is not broken.
|
// chain is not broken.
|
||||||
logForDebugging(
|
logForDebugging(
|
||||||
`[${label}:token] Could not decode JWT expiry for sessionId=${sessionId}, token prefix=${token.slice(0, 15)}…, keeping existing timer`,
|
`[${label}:token] Could not decode JWT expiry for sessionId=${sessionId}, keeping existing timer`,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -209,7 +209,7 @@ export function createTokenRefreshScheduler({
|
|||||||
failureCounts.delete(sessionId)
|
failureCounts.delete(sessionId)
|
||||||
|
|
||||||
logForDebugging(
|
logForDebugging(
|
||||||
`[${label}:token] Refreshing token for sessionId=${sessionId}: new token prefix=${oauthToken.slice(0, 15)}…`,
|
`[${label}:token] Refreshing token for sessionId=${sessionId}`,
|
||||||
)
|
)
|
||||||
logEvent('tengu_bridge_token_refreshed', {})
|
logEvent('tengu_bridge_token_refreshed', {})
|
||||||
onRefresh(sessionId, oauthToken)
|
onRefresh(sessionId, oauthToken)
|
||||||
|
|||||||
@@ -50,7 +50,10 @@ import {
|
|||||||
extractTitleText,
|
extractTitleText,
|
||||||
BoundedUUIDSet,
|
BoundedUUIDSet,
|
||||||
} from './bridgeMessaging.js'
|
} from './bridgeMessaging.js'
|
||||||
import { logBridgeSkip } from './debugUtils.js'
|
import {
|
||||||
|
logBridgeSkip,
|
||||||
|
summarizeBridgeErrorForDebug,
|
||||||
|
} from './debugUtils.js'
|
||||||
import { logForDebugging } from '../utils/debug.js'
|
import { logForDebugging } from '../utils/debug.js'
|
||||||
import { logForDiagnosticsNoPII } from '../utils/diagLogs.js'
|
import { logForDiagnosticsNoPII } from '../utils/diagLogs.js'
|
||||||
import { isInProtectedNamespace } from '../utils/envUtils.js'
|
import { isInProtectedNamespace } from '../utils/envUtils.js'
|
||||||
@@ -235,10 +238,12 @@ export async function initEnvLessBridgeCore(
|
|||||||
})
|
})
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logForDebugging(
|
logForDebugging(
|
||||||
`[remote-bridge] v2 transport setup failed: ${errorMessage(err)}`,
|
`[remote-bridge] v2 transport setup failed: ${summarizeBridgeErrorForDebug(
|
||||||
|
err,
|
||||||
|
)}`,
|
||||||
{ level: 'error' },
|
{ level: 'error' },
|
||||||
)
|
)
|
||||||
onStateChange?.('failed', `Transport setup failed: ${errorMessage(err)}`)
|
onStateChange?.('failed', 'Transport setup failed')
|
||||||
logBridgeSkip('v2_transport_setup_failed', undefined, true)
|
logBridgeSkip('v2_transport_setup_failed', undefined, true)
|
||||||
void archiveSession(
|
void archiveSession(
|
||||||
sessionId,
|
sessionId,
|
||||||
@@ -356,7 +361,9 @@ export async function initEnvLessBridgeCore(
|
|||||||
)
|
)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logForDebugging(
|
logForDebugging(
|
||||||
`[remote-bridge] Proactive refresh rebuild failed: ${errorMessage(err)}`,
|
`[remote-bridge] Proactive refresh rebuild failed: ${summarizeBridgeErrorForDebug(
|
||||||
|
err,
|
||||||
|
)}`,
|
||||||
{ level: 'error' },
|
{ level: 'error' },
|
||||||
)
|
)
|
||||||
logForDiagnosticsNoPII(
|
logForDiagnosticsNoPII(
|
||||||
@@ -364,7 +371,7 @@ export async function initEnvLessBridgeCore(
|
|||||||
'bridge_repl_v2_proactive_refresh_failed',
|
'bridge_repl_v2_proactive_refresh_failed',
|
||||||
)
|
)
|
||||||
if (!tornDown) {
|
if (!tornDown) {
|
||||||
onStateChange?.('failed', `Refresh failed: ${errorMessage(err)}`)
|
onStateChange?.('failed', 'Refresh failed')
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
authRecoveryInFlight = false
|
authRecoveryInFlight = false
|
||||||
@@ -394,9 +401,13 @@ export async function initEnvLessBridgeCore(
|
|||||||
// (Same guard pattern as replBridge.ts:1119.)
|
// (Same guard pattern as replBridge.ts:1119.)
|
||||||
const flushTransport = transport
|
const flushTransport = transport
|
||||||
void flushHistory(initialMessages)
|
void flushHistory(initialMessages)
|
||||||
.catch(e =>
|
.catch(e => {
|
||||||
logForDebugging(`[remote-bridge] flushHistory failed: ${e}`),
|
logForDebugging(
|
||||||
)
|
`[remote-bridge] flushHistory failed: ${summarizeBridgeErrorForDebug(
|
||||||
|
e,
|
||||||
|
)}`,
|
||||||
|
)
|
||||||
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
// authRecoveryInFlight catches the v1-vs-v2 asymmetry: v1 nulls
|
// authRecoveryInFlight catches the v1-vs-v2 asymmetry: v1 nulls
|
||||||
// transport synchronously in setOnClose (replBridge.ts:1175), so
|
// transport synchronously in setOnClose (replBridge.ts:1175), so
|
||||||
@@ -576,12 +587,14 @@ export async function initEnvLessBridgeCore(
|
|||||||
logForDebugging('[remote-bridge] Transport rebuilt after 401')
|
logForDebugging('[remote-bridge] Transport rebuilt after 401')
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logForDebugging(
|
logForDebugging(
|
||||||
`[remote-bridge] 401 recovery failed: ${errorMessage(err)}`,
|
`[remote-bridge] 401 recovery failed: ${summarizeBridgeErrorForDebug(
|
||||||
|
err,
|
||||||
|
)}`,
|
||||||
{ level: 'error' },
|
{ level: 'error' },
|
||||||
)
|
)
|
||||||
logForDiagnosticsNoPII('error', 'bridge_repl_v2_jwt_refresh_failed')
|
logForDiagnosticsNoPII('error', 'bridge_repl_v2_jwt_refresh_failed')
|
||||||
if (!tornDown) {
|
if (!tornDown) {
|
||||||
onStateChange?.('failed', `JWT refresh failed: ${errorMessage(err)}`)
|
onStateChange?.('failed', 'JWT refresh failed')
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
authRecoveryInFlight = false
|
authRecoveryInFlight = false
|
||||||
@@ -706,7 +719,9 @@ export async function initEnvLessBridgeCore(
|
|||||||
)
|
)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logForDebugging(
|
logForDebugging(
|
||||||
`[remote-bridge] Teardown 401 retry threw: ${errorMessage(err)}`,
|
`[remote-bridge] Teardown 401 retry threw: ${summarizeBridgeErrorForDebug(
|
||||||
|
err,
|
||||||
|
)}`,
|
||||||
{ level: 'error' },
|
{ level: 'error' },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -823,7 +838,7 @@ export async function initEnvLessBridgeCore(
|
|||||||
sendControlRequest(request: SDKControlRequest) {
|
sendControlRequest(request: SDKControlRequest) {
|
||||||
if (authRecoveryInFlight) {
|
if (authRecoveryInFlight) {
|
||||||
logForDebugging(
|
logForDebugging(
|
||||||
`[remote-bridge] Dropping control_request during 401 recovery: ${request.request_id}`,
|
'[remote-bridge] Dropping control_request during 401 recovery',
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -832,9 +847,7 @@ export async function initEnvLessBridgeCore(
|
|||||||
transport.reportState('requires_action')
|
transport.reportState('requires_action')
|
||||||
}
|
}
|
||||||
void transport.write(event)
|
void transport.write(event)
|
||||||
logForDebugging(
|
logForDebugging('[remote-bridge] Sent control_request')
|
||||||
`[remote-bridge] Sent control_request request_id=${request.request_id}`,
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
sendControlResponse(response: SDKControlResponse) {
|
sendControlResponse(response: SDKControlResponse) {
|
||||||
if (authRecoveryInFlight) {
|
if (authRecoveryInFlight) {
|
||||||
@@ -851,7 +864,7 @@ export async function initEnvLessBridgeCore(
|
|||||||
sendControlCancelRequest(requestId: string) {
|
sendControlCancelRequest(requestId: string) {
|
||||||
if (authRecoveryInFlight) {
|
if (authRecoveryInFlight) {
|
||||||
logForDebugging(
|
logForDebugging(
|
||||||
`[remote-bridge] Dropping control_cancel_request during 401 recovery: ${requestId}`,
|
'[remote-bridge] Dropping control_cancel_request during 401 recovery',
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -865,9 +878,7 @@ export async function initEnvLessBridgeCore(
|
|||||||
// those paths, so without this the server stays on requires_action.
|
// those paths, so without this the server stays on requires_action.
|
||||||
transport.reportState('running')
|
transport.reportState('running')
|
||||||
void transport.write(event)
|
void transport.write(event)
|
||||||
logForDebugging(
|
logForDebugging('[remote-bridge] Sent control_cancel_request')
|
||||||
`[remote-bridge] Sent control_cancel_request request_id=${requestId}`,
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
sendResult() {
|
sendResult() {
|
||||||
if (authRecoveryInFlight) {
|
if (authRecoveryInFlight) {
|
||||||
@@ -876,7 +887,7 @@ export async function initEnvLessBridgeCore(
|
|||||||
}
|
}
|
||||||
transport.reportState('idle')
|
transport.reportState('idle')
|
||||||
void transport.write(makeResultMessage(sessionId))
|
void transport.write(makeResultMessage(sessionId))
|
||||||
logForDebugging(`[remote-bridge] Sent result`)
|
logForDebugging('[remote-bridge] Sent result')
|
||||||
},
|
},
|
||||||
async teardown() {
|
async teardown() {
|
||||||
unregister()
|
unregister()
|
||||||
@@ -992,12 +1003,13 @@ async function archiveSession(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
logForDebugging(
|
logForDebugging(
|
||||||
`[remote-bridge] Archive ${compatId} status=${response.status}`,
|
`[remote-bridge] Archive status=${response.status}`,
|
||||||
)
|
)
|
||||||
return response.status
|
return response.status
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const msg = errorMessage(err)
|
logForDebugging(
|
||||||
logForDebugging(`[remote-bridge] Archive failed: ${msg}`)
|
`[remote-bridge] Archive failed: ${summarizeBridgeErrorForDebug(err)}`,
|
||||||
|
)
|
||||||
return axios.isAxiosError(err) && err.code === 'ECONNABORTED'
|
return axios.isAxiosError(err) && err.code === 'ECONNABORTED'
|
||||||
? 'timeout'
|
? 'timeout'
|
||||||
: 'error'
|
: 'error'
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ import {
|
|||||||
describeAxiosError,
|
describeAxiosError,
|
||||||
extractHttpStatus,
|
extractHttpStatus,
|
||||||
logBridgeSkip,
|
logBridgeSkip,
|
||||||
|
summarizeBridgeErrorForDebug,
|
||||||
} from './debugUtils.js'
|
} from './debugUtils.js'
|
||||||
import type { Message } from '../types/message.js'
|
import type { Message } from '../types/message.js'
|
||||||
import type { SDKMessage } from '../entrypoints/agentSdkTypes.ts'
|
import type { SDKMessage } from '../entrypoints/agentSdkTypes.ts'
|
||||||
@@ -303,7 +304,7 @@ export async function initBridgeCore(
|
|||||||
const prior = rawPrior?.source === 'repl' ? rawPrior : null
|
const prior = rawPrior?.source === 'repl' ? rawPrior : null
|
||||||
|
|
||||||
logForDebugging(
|
logForDebugging(
|
||||||
`[bridge:repl] initBridgeCore #${seq} starting (initialMessages=${initialMessages?.length ?? 0}${prior ? ` perpetual prior=env:${prior.environmentId}` : ''})`,
|
`[bridge:repl] initBridgeCore #${seq} starting (initialMessages=${initialMessages?.length ?? 0}${prior ? ' perpetual prior pointer present' : ''})`,
|
||||||
)
|
)
|
||||||
|
|
||||||
// 5. Register bridge environment
|
// 5. Register bridge environment
|
||||||
@@ -342,7 +343,9 @@ export async function initBridgeCore(
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
logBridgeSkip(
|
logBridgeSkip(
|
||||||
'registration_failed',
|
'registration_failed',
|
||||||
`[bridge:repl] Environment registration failed: ${errorMessage(err)}`,
|
`[bridge:repl] Environment registration failed: ${summarizeBridgeErrorForDebug(
|
||||||
|
err,
|
||||||
|
)}`,
|
||||||
)
|
)
|
||||||
// Stale pointer may be the cause (expired/deleted env) — clear it so
|
// Stale pointer may be the cause (expired/deleted env) — clear it so
|
||||||
// the next start doesn't retry the same dead ID.
|
// the next start doesn't retry the same dead ID.
|
||||||
@@ -353,7 +356,7 @@ export async function initBridgeCore(
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
logForDebugging(`[bridge:repl] Environment registered: ${environmentId}`)
|
logForDebugging('[bridge:repl] Environment registered')
|
||||||
logForDiagnosticsNoPII('info', 'bridge_repl_env_registered')
|
logForDiagnosticsNoPII('info', 'bridge_repl_env_registered')
|
||||||
logEvent('tengu_bridge_repl_env_registered', {})
|
logEvent('tengu_bridge_repl_env_registered', {})
|
||||||
|
|
||||||
@@ -371,7 +374,7 @@ export async function initBridgeCore(
|
|||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
if (environmentId !== requestedEnvId) {
|
if (environmentId !== requestedEnvId) {
|
||||||
logForDebugging(
|
logForDebugging(
|
||||||
`[bridge:repl] Env mismatch (requested ${requestedEnvId}, got ${environmentId}) — cannot reconnect in place`,
|
'[bridge:repl] Env mismatch — cannot reconnect in place',
|
||||||
)
|
)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -389,13 +392,13 @@ export async function initBridgeCore(
|
|||||||
for (const id of candidates) {
|
for (const id of candidates) {
|
||||||
try {
|
try {
|
||||||
await api.reconnectSession(environmentId, id)
|
await api.reconnectSession(environmentId, id)
|
||||||
logForDebugging(
|
logForDebugging('[bridge:repl] Reconnected existing session in place')
|
||||||
`[bridge:repl] Reconnected session ${id} in place on env ${environmentId}`,
|
|
||||||
)
|
|
||||||
return true
|
return true
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logForDebugging(
|
logForDebugging(
|
||||||
`[bridge:repl] reconnectSession(${id}) failed: ${errorMessage(err)}`,
|
`[bridge:repl] reconnectSession failed: ${summarizeBridgeErrorForDebug(
|
||||||
|
err,
|
||||||
|
)}`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -679,7 +682,9 @@ export async function initBridgeCore(
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
bridgeConfig.reuseEnvironmentId = undefined
|
bridgeConfig.reuseEnvironmentId = undefined
|
||||||
logForDebugging(
|
logForDebugging(
|
||||||
`[bridge:repl] Environment re-registration failed: ${errorMessage(err)}`,
|
`[bridge:repl] Environment re-registration failed: ${summarizeBridgeErrorForDebug(
|
||||||
|
err,
|
||||||
|
)}`,
|
||||||
)
|
)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -688,7 +693,7 @@ export async function initBridgeCore(
|
|||||||
bridgeConfig.reuseEnvironmentId = undefined
|
bridgeConfig.reuseEnvironmentId = undefined
|
||||||
|
|
||||||
logForDebugging(
|
logForDebugging(
|
||||||
`[bridge:repl] Re-registered: requested=${requestedEnvId} got=${environmentId}`,
|
'[bridge:repl] Re-registered environment',
|
||||||
)
|
)
|
||||||
|
|
||||||
// Bail out if teardown started while we were registering
|
// Bail out if teardown started while we were registering
|
||||||
@@ -984,7 +989,7 @@ export async function initBridgeCore(
|
|||||||
injectFault: injectBridgeFault,
|
injectFault: injectBridgeFault,
|
||||||
wakePollLoop,
|
wakePollLoop,
|
||||||
describe: () =>
|
describe: () =>
|
||||||
`env=${environmentId} session=${currentSessionId} transport=${transport?.getStateLabel() ?? 'null'} workId=${currentWorkId ?? 'null'}`,
|
`transport=${transport?.getStateLabel() ?? 'null'} hasSession=${Boolean(currentSessionId)} hasWork=${Boolean(currentWorkId)}`,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1038,7 +1043,9 @@ export async function initBridgeCore(
|
|||||||
.stopWork(environmentId, currentWorkId, false)
|
.stopWork(environmentId, currentWorkId, false)
|
||||||
.catch((e: unknown) => {
|
.catch((e: unknown) => {
|
||||||
logForDebugging(
|
logForDebugging(
|
||||||
`[bridge:repl] stopWork after heartbeat fatal: ${errorMessage(e)}`,
|
`[bridge:repl] stopWork after heartbeat fatal: ${summarizeBridgeErrorForDebug(
|
||||||
|
e,
|
||||||
|
)}`,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -1365,7 +1372,7 @@ export async function initBridgeCore(
|
|||||||
const sessionUrl = buildCCRv2SdkUrl(baseUrl, workSessionId)
|
const sessionUrl = buildCCRv2SdkUrl(baseUrl, workSessionId)
|
||||||
const thisGen = v2Generation
|
const thisGen = v2Generation
|
||||||
logForDebugging(
|
logForDebugging(
|
||||||
`[bridge:repl] CCR v2: creating transport for session=${workSessionId} gen=${thisGen}`,
|
`[bridge:repl] CCR v2: creating transport gen=${thisGen}`,
|
||||||
)
|
)
|
||||||
void createV2ReplTransport({
|
void createV2ReplTransport({
|
||||||
sessionUrl,
|
sessionUrl,
|
||||||
@@ -1399,7 +1406,9 @@ export async function initBridgeCore(
|
|||||||
},
|
},
|
||||||
(err: unknown) => {
|
(err: unknown) => {
|
||||||
logForDebugging(
|
logForDebugging(
|
||||||
`[bridge:repl] CCR v2: createV2ReplTransport failed: ${errorMessage(err)}`,
|
`[bridge:repl] CCR v2: createV2ReplTransport failed: ${summarizeBridgeErrorForDebug(
|
||||||
|
err,
|
||||||
|
)}`,
|
||||||
{ level: 'error' },
|
{ level: 'error' },
|
||||||
)
|
)
|
||||||
logEvent('tengu_bridge_repl_ccr_v2_init_failed', {})
|
logEvent('tengu_bridge_repl_ccr_v2_init_failed', {})
|
||||||
@@ -1414,7 +1423,9 @@ export async function initBridgeCore(
|
|||||||
.stopWork(environmentId, currentWorkId, false)
|
.stopWork(environmentId, currentWorkId, false)
|
||||||
.catch((e: unknown) => {
|
.catch((e: unknown) => {
|
||||||
logForDebugging(
|
logForDebugging(
|
||||||
`[bridge:repl] stopWork after v2 init failure: ${errorMessage(e)}`,
|
`[bridge:repl] stopWork after v2 init failure: ${summarizeBridgeErrorForDebug(
|
||||||
|
e,
|
||||||
|
)}`,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
currentWorkId = null
|
currentWorkId = null
|
||||||
@@ -1436,9 +1447,7 @@ export async function initBridgeCore(
|
|||||||
// WS reconnect attempt.
|
// WS reconnect attempt.
|
||||||
const wsUrl = buildSdkUrl(sessionIngressUrl, workSessionId)
|
const wsUrl = buildSdkUrl(sessionIngressUrl, workSessionId)
|
||||||
logForDebugging('[bridge:repl] Using session ingress WebSocket endpoint')
|
logForDebugging('[bridge:repl] Using session ingress WebSocket endpoint')
|
||||||
logForDebugging(
|
logForDebugging('[bridge:repl] Creating HybridTransport')
|
||||||
`[bridge:repl] Creating HybridTransport: session=${workSessionId}`,
|
|
||||||
)
|
|
||||||
// v1OauthToken was validated non-null above (we'd have returned early).
|
// v1OauthToken was validated non-null above (we'd have returned early).
|
||||||
const oauthToken = v1OauthToken ?? ''
|
const oauthToken = v1OauthToken ?? ''
|
||||||
wireTransport(
|
wireTransport(
|
||||||
@@ -1523,7 +1532,9 @@ export async function initBridgeCore(
|
|||||||
logForDebugging('[bridge:repl] keep_alive sent')
|
logForDebugging('[bridge:repl] keep_alive sent')
|
||||||
void transport.write({ type: 'keep_alive' }).catch((err: unknown) => {
|
void transport.write({ type: 'keep_alive' }).catch((err: unknown) => {
|
||||||
logForDebugging(
|
logForDebugging(
|
||||||
`[bridge:repl] keep_alive write failed: ${errorMessage(err)}`,
|
`[bridge:repl] keep_alive write failed: ${summarizeBridgeErrorForDebug(
|
||||||
|
err,
|
||||||
|
)}`,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}, keepAliveIntervalMs)
|
}, keepAliveIntervalMs)
|
||||||
@@ -1536,15 +1547,13 @@ export async function initBridgeCore(
|
|||||||
doTeardownImpl = async (): Promise<void> => {
|
doTeardownImpl = async (): Promise<void> => {
|
||||||
if (teardownStarted) {
|
if (teardownStarted) {
|
||||||
logForDebugging(
|
logForDebugging(
|
||||||
`[bridge:repl] Teardown already in progress, skipping duplicate call env=${environmentId} session=${currentSessionId}`,
|
'[bridge:repl] Teardown already in progress, skipping duplicate call',
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
teardownStarted = true
|
teardownStarted = true
|
||||||
const teardownStart = Date.now()
|
const teardownStart = Date.now()
|
||||||
logForDebugging(
|
logForDebugging('[bridge:repl] Teardown starting')
|
||||||
`[bridge:repl] Teardown starting: env=${environmentId} session=${currentSessionId} workId=${currentWorkId ?? 'none'} transportState=${transport?.getStateLabel() ?? 'null'}`,
|
|
||||||
)
|
|
||||||
|
|
||||||
if (pointerRefreshTimer !== null) {
|
if (pointerRefreshTimer !== null) {
|
||||||
clearInterval(pointerRefreshTimer)
|
clearInterval(pointerRefreshTimer)
|
||||||
@@ -1593,7 +1602,7 @@ export async function initBridgeCore(
|
|||||||
source: 'repl',
|
source: 'repl',
|
||||||
})
|
})
|
||||||
logForDebugging(
|
logForDebugging(
|
||||||
`[bridge:repl] Teardown (perpetual): leaving env=${environmentId} session=${currentSessionId} alive on server, duration=${Date.now() - teardownStart}ms`,
|
`[bridge:repl] Teardown (perpetual): leaving bridge session alive on server, duration=${Date.now() - teardownStart}ms`,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1619,7 +1628,9 @@ export async function initBridgeCore(
|
|||||||
})
|
})
|
||||||
.catch((err: unknown) => {
|
.catch((err: unknown) => {
|
||||||
logForDebugging(
|
logForDebugging(
|
||||||
`[bridge:repl] Teardown stopWork failed: ${errorMessage(err)}`,
|
`[bridge:repl] Teardown stopWork failed: ${summarizeBridgeErrorForDebug(
|
||||||
|
err,
|
||||||
|
)}`,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
: Promise.resolve()
|
: Promise.resolve()
|
||||||
@@ -1636,7 +1647,9 @@ export async function initBridgeCore(
|
|||||||
|
|
||||||
await api.deregisterEnvironment(environmentId).catch((err: unknown) => {
|
await api.deregisterEnvironment(environmentId).catch((err: unknown) => {
|
||||||
logForDebugging(
|
logForDebugging(
|
||||||
`[bridge:repl] Teardown deregister failed: ${errorMessage(err)}`,
|
`[bridge:repl] Teardown deregister failed: ${summarizeBridgeErrorForDebug(
|
||||||
|
err,
|
||||||
|
)}`,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -1646,16 +1659,14 @@ export async function initBridgeCore(
|
|||||||
await clearBridgePointer(dir)
|
await clearBridgePointer(dir)
|
||||||
|
|
||||||
logForDebugging(
|
logForDebugging(
|
||||||
`[bridge:repl] Teardown complete: env=${environmentId} duration=${Date.now() - teardownStart}ms`,
|
`[bridge:repl] Teardown complete: duration=${Date.now() - teardownStart}ms`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 8. Register cleanup for graceful shutdown
|
// 8. Register cleanup for graceful shutdown
|
||||||
const unregister = registerCleanup(() => doTeardownImpl?.())
|
const unregister = registerCleanup(() => doTeardownImpl?.())
|
||||||
|
|
||||||
logForDebugging(
|
logForDebugging('[bridge:repl] Ready')
|
||||||
`[bridge:repl] Ready: env=${environmentId} session=${currentSessionId}`,
|
|
||||||
)
|
|
||||||
onStateChange?.('ready')
|
onStateChange?.('ready')
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -1713,7 +1724,7 @@ export async function initBridgeCore(
|
|||||||
if (!transport) {
|
if (!transport) {
|
||||||
const types = filtered.map(m => m.type).join(',')
|
const types = filtered.map(m => m.type).join(',')
|
||||||
logForDebugging(
|
logForDebugging(
|
||||||
`[bridge:repl] Transport not configured, dropping ${filtered.length} message(s) [${types}] for session=${currentSessionId}`,
|
`[bridge:repl] Transport not configured, dropping ${filtered.length} message(s) [${types}]`,
|
||||||
{ level: 'warn' },
|
{ level: 'warn' },
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
@@ -1748,7 +1759,7 @@ export async function initBridgeCore(
|
|||||||
if (filtered.length === 0) return
|
if (filtered.length === 0) return
|
||||||
if (!transport) {
|
if (!transport) {
|
||||||
logForDebugging(
|
logForDebugging(
|
||||||
`[bridge:repl] Transport not configured, dropping ${filtered.length} SDK message(s) for session=${currentSessionId}`,
|
`[bridge:repl] Transport not configured, dropping ${filtered.length} SDK message(s)`,
|
||||||
{ level: 'warn' },
|
{ level: 'warn' },
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
@@ -1768,9 +1779,7 @@ export async function initBridgeCore(
|
|||||||
}
|
}
|
||||||
const event = { ...request, session_id: currentSessionId }
|
const event = { ...request, session_id: currentSessionId }
|
||||||
void transport.write(event)
|
void transport.write(event)
|
||||||
logForDebugging(
|
logForDebugging('[bridge:repl] Sent control_request')
|
||||||
`[bridge:repl] Sent control_request request_id=${request.request_id}`,
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
sendControlResponse(response: SDKControlResponse) {
|
sendControlResponse(response: SDKControlResponse) {
|
||||||
if (!transport) {
|
if (!transport) {
|
||||||
@@ -1796,21 +1805,17 @@ export async function initBridgeCore(
|
|||||||
session_id: currentSessionId,
|
session_id: currentSessionId,
|
||||||
}
|
}
|
||||||
void transport.write(event)
|
void transport.write(event)
|
||||||
logForDebugging(
|
logForDebugging('[bridge:repl] Sent control_cancel_request')
|
||||||
`[bridge:repl] Sent control_cancel_request request_id=${requestId}`,
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
sendResult() {
|
sendResult() {
|
||||||
if (!transport) {
|
if (!transport) {
|
||||||
logForDebugging(
|
logForDebugging(
|
||||||
`[bridge:repl] sendResult: skipping, transport not configured session=${currentSessionId}`,
|
'[bridge:repl] sendResult: skipping, transport not configured',
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
void transport.write(makeResultMessage(currentSessionId))
|
void transport.write(makeResultMessage(currentSessionId))
|
||||||
logForDebugging(
|
logForDebugging('[bridge:repl] Sent result')
|
||||||
`[bridge:repl] Sent result for session=${currentSessionId}`,
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
async teardown() {
|
async teardown() {
|
||||||
unregister()
|
unregister()
|
||||||
@@ -1903,7 +1908,7 @@ async function startWorkPollLoop({
|
|||||||
const MAX_ENVIRONMENT_RECREATIONS = 3
|
const MAX_ENVIRONMENT_RECREATIONS = 3
|
||||||
|
|
||||||
logForDebugging(
|
logForDebugging(
|
||||||
`[bridge:repl] Starting work poll loop for env=${getCredentials().environmentId}`,
|
'[bridge:repl] Starting work poll loop',
|
||||||
)
|
)
|
||||||
|
|
||||||
let consecutiveErrors = 0
|
let consecutiveErrors = 0
|
||||||
@@ -2006,7 +2011,9 @@ async function startWorkPollLoop({
|
|||||||
)
|
)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logForDebugging(
|
logForDebugging(
|
||||||
`[bridge:repl:heartbeat] Failed: ${errorMessage(err)}`,
|
`[bridge:repl:heartbeat] Failed: ${summarizeBridgeErrorForDebug(
|
||||||
|
err,
|
||||||
|
)}`,
|
||||||
)
|
)
|
||||||
if (err instanceof BridgeFatalError) {
|
if (err instanceof BridgeFatalError) {
|
||||||
cap.cleanup()
|
cap.cleanup()
|
||||||
@@ -2124,7 +2131,9 @@ async function startWorkPollLoop({
|
|||||||
secret = decodeWorkSecret(work.secret)
|
secret = decodeWorkSecret(work.secret)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logForDebugging(
|
logForDebugging(
|
||||||
`[bridge:repl] Failed to decode work secret: ${errorMessage(err)}`,
|
`[bridge:repl] Failed to decode work secret: ${summarizeBridgeErrorForDebug(
|
||||||
|
err,
|
||||||
|
)}`,
|
||||||
)
|
)
|
||||||
logEvent('tengu_bridge_repl_work_secret_failed', {})
|
logEvent('tengu_bridge_repl_work_secret_failed', {})
|
||||||
// Can't ack (needs the JWT we failed to decode). stopWork uses OAuth.
|
// Can't ack (needs the JWT we failed to decode). stopWork uses OAuth.
|
||||||
@@ -2135,12 +2144,14 @@ async function startWorkPollLoop({
|
|||||||
|
|
||||||
// Explicitly acknowledge to prevent redelivery. Non-fatal on failure:
|
// Explicitly acknowledge to prevent redelivery. Non-fatal on failure:
|
||||||
// server re-delivers, and the onWorkReceived callback handles dedup.
|
// server re-delivers, and the onWorkReceived callback handles dedup.
|
||||||
logForDebugging(`[bridge:repl] Acknowledging workId=${work.id}`)
|
logForDebugging('[bridge:repl] Acknowledging work item')
|
||||||
try {
|
try {
|
||||||
await api.acknowledgeWork(envId, work.id, secret.session_ingress_token)
|
await api.acknowledgeWork(envId, work.id, secret.session_ingress_token)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logForDebugging(
|
logForDebugging(
|
||||||
`[bridge:repl] Acknowledge failed workId=${work.id}: ${errorMessage(err)}`,
|
`[bridge:repl] Acknowledge failed: ${summarizeBridgeErrorForDebug(
|
||||||
|
err,
|
||||||
|
)}`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2192,7 +2203,7 @@ async function startWorkPollLoop({
|
|||||||
const currentEnvId = getCredentials().environmentId
|
const currentEnvId = getCredentials().environmentId
|
||||||
if (envId !== currentEnvId) {
|
if (envId !== currentEnvId) {
|
||||||
logForDebugging(
|
logForDebugging(
|
||||||
`[bridge:repl] Stale poll error for old env=${envId}, current env=${currentEnvId} — skipping onEnvironmentLost`,
|
'[bridge:repl] Stale poll error for superseded environment — skipping onEnvironmentLost',
|
||||||
)
|
)
|
||||||
consecutiveErrors = 0
|
consecutiveErrors = 0
|
||||||
firstErrorTime = null
|
firstErrorTime = null
|
||||||
@@ -2238,9 +2249,7 @@ async function startWorkPollLoop({
|
|||||||
consecutiveErrors = 0
|
consecutiveErrors = 0
|
||||||
firstErrorTime = null
|
firstErrorTime = null
|
||||||
onStateChange?.('ready')
|
onStateChange?.('ready')
|
||||||
logForDebugging(
|
logForDebugging('[bridge:repl] Re-registered environment')
|
||||||
`[bridge:repl] Re-registered environment: ${newCreds.environmentId}`,
|
|
||||||
)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2376,7 +2385,7 @@ async function startWorkPollLoop({
|
|||||||
}
|
}
|
||||||
|
|
||||||
logForDebugging(
|
logForDebugging(
|
||||||
`[bridge:repl] Work poll loop ended (aborted=${signal.aborted}) env=${getCredentials().environmentId}`,
|
`[bridge:repl] Work poll loop ended (aborted=${signal.aborted})`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,33 @@ import axios from 'axios'
|
|||||||
import { jsonParse, jsonStringify } from '../utils/slowOperations.js'
|
import { jsonParse, jsonStringify } from '../utils/slowOperations.js'
|
||||||
import type { WorkSecret } from './types.js'
|
import type { WorkSecret } from './types.js'
|
||||||
|
|
||||||
|
function summarizeRegisterWorkerResponseForDebug(data: unknown): string {
|
||||||
|
if (data === null) return 'null'
|
||||||
|
if (data === undefined) return 'undefined'
|
||||||
|
if (Array.isArray(data)) {
|
||||||
|
return jsonStringify({
|
||||||
|
payloadType: 'array',
|
||||||
|
length: data.length,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (typeof data === 'object') {
|
||||||
|
const value = data as Record<string, unknown>
|
||||||
|
return jsonStringify({
|
||||||
|
payloadType: 'object',
|
||||||
|
keys: Object.keys(value)
|
||||||
|
.sort()
|
||||||
|
.slice(0, 10),
|
||||||
|
hasWorkerEpoch:
|
||||||
|
typeof value.worker_epoch === 'number' ||
|
||||||
|
typeof value.worker_epoch === 'string',
|
||||||
|
hasSessionIngressToken:
|
||||||
|
typeof value.session_ingress_token === 'string',
|
||||||
|
hasApiBaseUrl: typeof value.api_base_url === 'string',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return typeof data
|
||||||
|
}
|
||||||
|
|
||||||
/** Decode a base64url-encoded work secret and validate its version. */
|
/** Decode a base64url-encoded work secret and validate its version. */
|
||||||
export function decodeWorkSecret(secret: string): WorkSecret {
|
export function decodeWorkSecret(secret: string): WorkSecret {
|
||||||
const json = Buffer.from(secret, 'base64url').toString('utf-8')
|
const json = Buffer.from(secret, 'base64url').toString('utf-8')
|
||||||
@@ -120,7 +147,9 @@ export async function registerWorker(
|
|||||||
!Number.isSafeInteger(epoch)
|
!Number.isSafeInteger(epoch)
|
||||||
) {
|
) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`registerWorker: invalid worker_epoch in response: ${jsonStringify(response.data)}`,
|
`registerWorker: invalid worker_epoch in response: ${summarizeRegisterWorkerResponseForDebug(
|
||||||
|
response.data,
|
||||||
|
)}`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return epoch
|
return epoch
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user