fix(code-mode): keep deep host boundaries iterative
This commit is contained in:
@@ -58,7 +58,8 @@ export function isAgentLoopRequest(request: GenerateOptions): boolean {
|
||||
}
|
||||
|
||||
/**
|
||||
* Deep-freeze a value in place, guarding cycles, so later mutation throws.
|
||||
* Deep-freeze a value in place with an iterative traversal, guarding cycles,
|
||||
* so later mutation throws without imposing a JavaScript call-stack depth cap.
|
||||
* {@link AbortSignal} objects are deliberately skipped because they are the
|
||||
* request's live cancellation channel and freezing them breaks abort.
|
||||
* @param value - the value to freeze in place.
|
||||
@@ -66,16 +67,31 @@ export function isAgentLoopRequest(request: GenerateOptions): boolean {
|
||||
*/
|
||||
export function deepFreeze<T>(value: T): T {
|
||||
const seen = new WeakSet<object>()
|
||||
const walk = (node: unknown): void => {
|
||||
if (node === null || typeof node !== 'object') return
|
||||
if (node instanceof AbortSignal) return
|
||||
if (seen.has(node)) return
|
||||
const pending: (
|
||||
| { kind: 'visit'; node: unknown }
|
||||
| { kind: 'property'; source: Record<string, unknown>; key: string }
|
||||
)[] = [{ kind: 'visit', node: value }]
|
||||
while (pending.length > 0) {
|
||||
const task = pending.pop()
|
||||
/* v8 ignore next -- the loop condition guarantees one pending task. */
|
||||
if (task === undefined) continue
|
||||
if (task.kind === 'property') {
|
||||
pending.push({ kind: 'visit', node: task.source[task.key] })
|
||||
continue
|
||||
}
|
||||
const node = task.node
|
||||
if (node === null || typeof node !== 'object') continue
|
||||
if (node instanceof AbortSignal) continue
|
||||
if (seen.has(node)) continue
|
||||
seen.add(node)
|
||||
Object.freeze(node)
|
||||
for (const key of Object.keys(node)) {
|
||||
walk((node as Record<string, unknown>)[key])
|
||||
const keys = Object.keys(node)
|
||||
for (let index = keys.length - 1; index >= 0; index--) {
|
||||
const key = keys[index]
|
||||
/* v8 ignore next -- the loop is bounded by the captured key count. */
|
||||
if (key === undefined) continue
|
||||
pending.push({ kind: 'property', source: node as Record<string, unknown>, key })
|
||||
}
|
||||
}
|
||||
walk(value)
|
||||
return value
|
||||
}
|
||||
|
||||
@@ -56,6 +56,26 @@ describe('deepFreeze', () => {
|
||||
deepFreeze(cyclic)
|
||||
expect(Object.isFrozen(cyclic)).toBe(true)
|
||||
})
|
||||
|
||||
it('freezes nesting deeper than the JavaScript call stack', () => {
|
||||
const depth = 5_000
|
||||
const root: unknown[] = []
|
||||
let cursor = root
|
||||
for (let index = 0; index < depth; index++) {
|
||||
const child: unknown[] = []
|
||||
cursor.push(child)
|
||||
cursor = child
|
||||
}
|
||||
|
||||
deepFreeze(root)
|
||||
|
||||
cursor = root
|
||||
for (let index = 0; index < depth; index++) {
|
||||
expect(Object.isFrozen(cursor)).toBe(true)
|
||||
cursor = cursor[0] as unknown[]
|
||||
}
|
||||
expect(Object.isFrozen(cursor)).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('agent-loop request identity', () => {
|
||||
|
||||
Reference in New Issue
Block a user