164 lines
16 KiB
JavaScript
164 lines
16 KiB
JavaScript
// Prepare a reviewable local REA 6.5.0 repair. Never reads WorkBuddy or changes global registration.
|
|
import fs from 'node:fs/promises'
|
|
import path from 'node:path'
|
|
import crypto from 'node:crypto'
|
|
|
|
const sourceRoot = path.resolve(process.argv[2] || 'C:/Users/Administrator/scoop/apps/nvm/current/nodejs/nodejs/node_modules/rea-agents')
|
|
const output = path.resolve('.tmp/rea-css-runtime')
|
|
const patchRoot = path.resolve('scripts/rea/patches')
|
|
const sourcePackage = JSON.parse(await fs.readFile(path.join(sourceRoot, 'package.json'), 'utf8'))
|
|
if (sourcePackage.name !== 'rea-agents' || sourcePackage.version !== '6.5.0') throw new Error('Repair requires installed rea-agents 6.5.0')
|
|
if (output === sourceRoot || !output.startsWith(path.resolve('.tmp') + path.sep)) throw new Error('Invalid local repair destination')
|
|
await fs.mkdir(output, { recursive: true })
|
|
const marker = path.join(output, 'css-repair-manifest.json')
|
|
try {
|
|
const previous = JSON.parse(await fs.readFile(marker, 'utf8'))
|
|
if (previous.sourceRoot !== sourceRoot) throw new Error('Local repair belongs to a different source installation')
|
|
} catch (error) {
|
|
if (error.code !== 'ENOENT') throw error
|
|
for (const entry of await fs.readdir(sourceRoot, { withFileTypes: true })) {
|
|
if (entry.name === 'node_modules') continue
|
|
await fs.cp(path.join(sourceRoot, entry.name), path.join(output, entry.name), { recursive: true, force: false, errorOnExist: true, verbatimSymlinks: true })
|
|
}
|
|
await fs.symlink(path.join(sourceRoot, 'node_modules'), path.join(output, 'node_modules'), process.platform === 'win32' ? 'junction' : 'dir')
|
|
}
|
|
const records = []
|
|
const digest = text => crypto.createHash('sha256').update(text).digest('hex')
|
|
async function patch(relative, transform) {
|
|
const original = await fs.readFile(path.join(sourceRoot, relative), 'utf8')
|
|
const modified = transform(original)
|
|
if (original === modified) throw new Error(`No change for ${relative}`)
|
|
const baseline = path.join(output, 'css-repair-baseline', relative)
|
|
await fs.mkdir(path.dirname(baseline), { recursive: true })
|
|
await fs.writeFile(baseline, original)
|
|
await fs.writeFile(path.join(output, relative), modified)
|
|
records.push({ path: relative, original_sha256: digest(original), modified_sha256: digest(modified) })
|
|
}
|
|
function once(text, before, after) {
|
|
if (text.split(before).length !== 2) throw new Error(`Expected exactly one patch anchor: ${before.slice(0, 70)}`)
|
|
return text.replace(before, after)
|
|
}
|
|
|
|
// Use the package's shipped unbundled build, so CLI and MCP load the repaired modules.
|
|
await patch('scripts/rea.mjs', text => once(text.replaceAll('dist/mcp/main.js', 'dist/main.js')
|
|
.replaceAll('dist/cli-runtime/cli.js', 'dist/cli.js')
|
|
.replaceAll('dist/cli-runtime/cliOutput.js', 'dist/cliOutput.js')
|
|
.replaceAll('dist/cli-runtime/cliResultOutput.js', 'dist/cli/cliResultOutput.js'), 'if (!(await compiledRuntimeExists(runtimeFiles))) {', `if (args[0] === "inspect-desktop-electron-processes") {
|
|
const { inspectDesktopElectronProcesses } = await import("../dist/browser/DesktopElectronProcesses.js");
|
|
process.stdout.write(JSON.stringify(await inspectDesktopElectronProcesses(args[1] || "WorkBuddy.exe")) + "\\n");
|
|
} else if (args[0] === "restart-desktop-electron-debug") {
|
|
const { restartDesktopElectronDebug } = await import("../dist/browser/DesktopElectronProcesses.js");
|
|
process.stdout.write(JSON.stringify(await restartDesktopElectronDebug(args[1], Number(args[2]), args.includes("--restart-authorized"))) + "\\n");
|
|
} else if (args[0] === "export-selected-artifact-text") {
|
|
const { exportSelectedArtifactText } = await import("../dist/artifacts/SelectedArtifactText.js");
|
|
process.stdout.write(JSON.stringify(await exportSelectedArtifactText(args[1], args[2], JSON.parse(args[3]))) + "\\n");
|
|
} else if (args[0] === "export-selected-artifact-binary") {
|
|
const { exportSelectedArtifactText } = await import("../dist/artifacts/SelectedArtifactText.js");
|
|
process.stdout.write(JSON.stringify(await exportSelectedArtifactText(args[1], args[2], JSON.parse(args[3]), true)) + "\\n");
|
|
} else if (args[0] === "export-selected-conversation-text") {
|
|
const { exportSelectedConversationText } = await import("../dist/artifacts/SelectedConversationText.js");
|
|
process.stdout.write(JSON.stringify(await exportSelectedConversationText(args[1], args[2], args.includes("--include-images"))) + "\\n");
|
|
} else if (!(await compiledRuntimeExists(runtimeFiles))) {`))
|
|
await patch('package.json', text => {
|
|
const pkg = JSON.parse(text)
|
|
pkg.version = '6.5.0-visual.3'
|
|
return JSON.stringify(pkg, null, 2) + '\n'
|
|
})
|
|
await patch('dist/generatedPackageMetadata.js', text => once(text, 'version: "6.5.0",', 'version: "6.5.0-visual.3",'))
|
|
await patch('dist/domain/javascript/electronObservation.js', text => {
|
|
text = 'import { electronVisualProbeInputSchema, electronMediaAssetsSchema, platformFontSampleSchema, visualProbeOutputSchema } from "./ElectronVisualProbeSchema.js";\nimport { webScreenshotArtifactSchema } from "../webScreenshot.js";\n' + text
|
|
text = once(text, ' target_id: z.string().trim().min(1),', ' target_id: z.string().trim().min(1),\n include_style_sources: z.boolean().default(false).describe("Include CSS text from stylesheets attributed to authorized Electron frames"),\n include_visuals: z.boolean().default(false).describe("Include visible viewport PNG and attributed DOM layout styles"),')
|
|
text = text.replace(' include_visuals: z.boolean()', ' visual_probe: electronVisualProbeInputSchema.optional(),\n include_visuals: z.boolean()')
|
|
text = once(text, ']);\nconst electronTargetSchema', `]).superRefine((value, ctx) => {
|
|
if (value.visual_probe && !value.include_visuals) ctx.addIssue({code: "custom", message: "visual_probe requires include_visuals"});
|
|
});
|
|
const electronTargetSchema`)
|
|
text = once(text, ' workers: z', ` visuals: z.object({
|
|
captured_at: z.iso.datetime(),
|
|
viewport: z.object({width:z.number().int().positive(),height:z.number().int().positive()}),
|
|
css_viewport: z.object({width:z.number().positive(),height:z.number().positive(),page_x:z.number(),page_y:z.number()}),
|
|
environment_media:z.object({reduced_motion:z.boolean().nullable()}).optional(),
|
|
environment_gpu:z.object({status:z.enum(['observed','unavailable']),devices:z.array(z.object({vendor_id:z.number(),device_id:z.number(),device_string:z.string().optional(),active:z.boolean().optional()})).optional(),aux_attributes:z.record(z.string(),z.union([z.string(),z.number(),z.boolean(),z.null()])).optional(),feature_status:z.record(z.string(),z.string()).optional()}).optional(),
|
|
artifact: webScreenshotArtifactSchema,
|
|
layout: z.object({properties:z.array(z.string()),documents:z.array(z.object({frame_id:z.string(),file_path:z.string(),scroll_x:z.number(),scroll_y:z.number(),nodes:z.array(z.object({node_index:z.number().int(),parent_index:z.number().int(),node_name:z.string(),attributes:z.record(z.string(),z.string()),bounds:z.tuple([z.number(),z.number(),z.number(),z.number()]),styles:z.record(z.string(),z.string())})),svg_nodes:z.array(z.object({node_index:z.number().int(),parent_index:z.number().int(),node_name:z.string(),attributes:z.record(z.string(),z.string())}))}))}),
|
|
limitations:z.array(z.string()),
|
|
font_samples:z.array(platformFontSampleSchema.extend({bounds:z.tuple([z.number(),z.number(),z.number(),z.number()]).optional(),styles:z.record(z.string(),z.string()).optional()})).optional(),
|
|
font_missing:z.array(z.object({category:z.string(),reason:z.string()})).optional(),
|
|
message_content:z.object({nodes:z.array(z.object({node_index:z.number().int(),parent_index:z.number().int(),node_name:z.string(),attributes:z.record(z.string(),z.string()),text:z.string().optional(),selector:z.string().optional()})),text_characters:z.number().int().min(0),truncated:z.boolean()}).optional(),
|
|
probe:visualProbeOutputSchema.optional(),
|
|
media_assets:electronMediaAssetsSchema.optional(),
|
|
}).optional(),
|
|
workers: z`)
|
|
return once(text, ' workers: z', ` styles: z.object({
|
|
total: z.number().int().min(0),
|
|
items: z.array(z.object({
|
|
style_sheet_key: prefixedDigestSchema("electron_stylesheet"),
|
|
style_sheet_id: z.string(), frame_id: z.string().nullable(), file_path: z.string().nullable(),
|
|
origin: z.string(), is_inline: z.boolean(), is_constructed: z.boolean(), source: electronSourceSchema,
|
|
})),
|
|
excluded: z.number().int().min(0), source_failures: z.number().int().min(0), truncated: z.boolean(),
|
|
limits: z.object({ max_sheets: z.number().int().positive(), max_sheet_bytes: z.number().int().positive(), max_total_bytes: z.number().int().positive() }),
|
|
}).optional(),
|
|
workers: z`)
|
|
})
|
|
await patch('dist/browser/CdpElectronInspection.js', text => {
|
|
text = 'import { captureElectronVisuals } from "./CdpElectronVisuals.js";\nimport { ingestElectronStyleEvent, captureElectronStyles } from "./CdpElectronStyles.js";\n' + text
|
|
text = once(text,'"REA does not evaluate renderer JavaScript, invoke Electron APIs, navigate, click, or close the page."','"REA does not evaluate renderer JavaScript or invoke Electron APIs; declared visual_probe native inputs are the only optional UI actions."')
|
|
text = once(text, ' scripts: [],', ' scripts: [],\n styles: new Map(),')
|
|
text = once(text, ' ingestElectronScriptEvent({', ' if (context.input.include_style_sources) ingestElectronStyleEvent(event, state.styles);\n ingestElectronScriptEvent({')
|
|
text = once(text, ' await enableElectronCapture(context);', ` await enableElectronCapture(context);
|
|
if (context.input.include_style_sources) {
|
|
await context.connection.send("DOM.enable", {}, context.sessionId, context.signal);
|
|
await context.connection.send("CSS.enable", {}, context.sessionId, context.signal);
|
|
state.limitations.push("CSS capture is a local REA 6.5.0-css.1 extension: only currently loaded styles attributed to authorized file-page frames are eligible. No renderer JavaScript is evaluated. Styles have independent coverage and budgets; inspect the styles section.");
|
|
}`)
|
|
return once(text, ' return { dom, scripts, resources, workers };', ` const styles = await captureElectronStyles({
|
|
connection: context.connection, sessionId: context.sessionId, signal: context.signal,
|
|
styles: state.styles, frameIds, request: context.input,
|
|
completeness: state.completeness, limitations: state.limitations,
|
|
});
|
|
const visuals = await captureElectronVisuals(context, frames);
|
|
return { dom, scripts, resources, workers, styles, ...(visuals ? {visuals} : {}) };`)
|
|
})
|
|
await patch('dist/browser/CdpElectronProvider.js', text => {
|
|
text = once(text, ' return CDP_ELECTRON_PROVIDER_IDENTITY;', ' return { ...CDP_ELECTRON_PROVIDER_IDENTITY, version: "1+visual-local.3" };')
|
|
return once(text, '["Debugger", "Runtime", "Page"]', '["CSS", "DOM", "Debugger", "Runtime", "Page"]')
|
|
})
|
|
await patch('dist/application/javascript/ElectronEvidence.js', text => once(once(text, 'authority: "external-service",','authority: input.visual_probe?.actions?.length ? "controlled-replay" : "external-service",'),
|
|
' include_script_sources: input.include_script_sources,',
|
|
' include_script_sources: input.include_script_sources,\n include_style_sources: input.include_style_sources,\n include_visuals: input.include_visuals,\n ...(input.visual_probe ? { visual_probe: input.visual_probe } : {}),'))
|
|
await patch('dist/cliObservationOptions.js', text => once(text,
|
|
'export const electronPageInspectionOptions = z.object({',
|
|
'export const electronPageInspectionOptions = z.object({\n visualProbe: z.string().optional().describe("JSON for declared native UI actions, sampled PNG frames, font usage and opt-in media_selectors for loaded IMG bytes"),\n includeStyleSources: z.boolean().default(false).describe("Include attributed local or inline stylesheet CSS text"),\n includeVisuals: z.boolean().default(false).describe("Include current viewport PNG and layout styles"),'))
|
|
await patch('dist/cli/electronCommands.js', text => once(text,
|
|
' include_script_sources: options.includeScriptSources,',
|
|
' include_script_sources: options.includeScriptSources,\n include_style_sources: options.includeStyleSources,\n include_visuals: options.includeVisuals,\n ...(options.visualProbe ? {visual_probe: JSON.parse(options.visualProbe)} : {}),'))
|
|
await patch('dist/contracts/javascript/electronToolContracts.js', text => once(text,
|
|
'Returns frames, DOM structure, resource paths, and scripts without evaluating renderer JavaScript or invoking Electron APIs.',
|
|
'Returns frames, DOM structure, resource paths, scripts and optional attributed CSS sources without evaluating renderer JavaScript or invoking Electron APIs. Default inspection is passive. include_style_sources selects CSS text; include_visuals selects viewport PNG and layout. Optional visual_probe declares native hover/click/scroll/key/viewport actions, sequential PNG samples, and actual platform fonts; UI actions can change application state and restoration is limited. visual_probe.media_selectors explicitly selects at most four unique main-frame IMG nodes and their already loaded PNG/JPEG/GIF/WebP/SVG resources, with byte budgets, SHA/base64 and visibility reported separately; no HTTP fetch is performed. These are local REA extensions; inspect coverage, budgets, restoration and limitations.'))
|
|
|
|
const styleModule = await fs.readFile(path.join(patchRoot, 'CdpElectronStyles.js'), 'utf8')
|
|
await fs.writeFile(path.join(output, 'dist/browser/CdpElectronStyles.js'), styleModule)
|
|
records.push({ path: 'dist/browser/CdpElectronStyles.js', original_sha256: null, modified_sha256: digest(styleModule) })
|
|
const visualModule = await fs.readFile(path.join(patchRoot, 'CdpElectronVisuals.js'), 'utf8')
|
|
await fs.writeFile(path.join(output, 'dist/browser/CdpElectronVisuals.js'), visualModule)
|
|
records.push({ path: 'dist/browser/CdpElectronVisuals.js', original_sha256: null, modified_sha256: digest(visualModule) })
|
|
const processModule = await fs.readFile(path.join(patchRoot, 'DesktopElectronProcesses.js'), 'utf8')
|
|
await fs.writeFile(path.join(output, 'dist/browser/DesktopElectronProcesses.js'), processModule)
|
|
records.push({ path: 'dist/browser/DesktopElectronProcesses.js', original_sha256: null, modified_sha256: digest(processModule) })
|
|
for(const [source,destination] of [['ElectronVisualProbeSchema.js','domain/javascript/ElectronVisualProbeSchema.js'],['CdpElectronVisualProbe.js','browser/CdpElectronVisualProbe.js'],['CdpElectronPlatformFonts.js','browser/CdpElectronPlatformFonts.js'],['CdpElectronMediaAssets.js','browser/CdpElectronMediaAssets.js']]) {
|
|
const module=await fs.readFile(path.join(patchRoot,source),'utf8')
|
|
await fs.writeFile(path.join(output,'dist',destination),module)
|
|
records.push({path:'dist/'+destination,original_sha256:null,modified_sha256:digest(module)})
|
|
}
|
|
const selectedArtifactModule=await fs.readFile(path.join(patchRoot,'SelectedArtifactText.js'),'utf8')
|
|
await fs.writeFile(path.join(output,'dist/artifacts/SelectedArtifactText.js'),selectedArtifactModule)
|
|
records.push({path:'dist/artifacts/SelectedArtifactText.js',original_sha256:null,modified_sha256:digest(selectedArtifactModule)})
|
|
const conversationModule=await fs.readFile(path.join(patchRoot,'SelectedConversationText.js'),'utf8')
|
|
await fs.writeFile(path.join(output,'dist/artifacts/SelectedConversationText.js'),conversationModule)
|
|
records.push({path:'dist/artifacts/SelectedConversationText.js',original_sha256:null,modified_sha256:digest(conversationModule)})
|
|
const manifest = { sourceRoot, upstreamVersion: sourcePackage.version, localVersion: '6.5.0-visual.3', output, files: records,
|
|
notes: ['Local REA repair, not an upstream release.', 'Global installation and Codex registration unchanged.', 'Public scripts/rea.mjs entry point loads the shipped unbundled build.', 'Dependencies reused read-only through node_modules link.'] }
|
|
await fs.writeFile(marker, JSON.stringify(manifest, null, 2) + '\n')
|
|
console.log(JSON.stringify({ output, localVersion: manifest.localVersion, files: records.length }))
|