minimal-agent 0.10.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -3
- package/package.json +52 -5
- package/plugins/computer_use/.claude-plugin/plugin.json +6 -0
- package/plugins/computer_use/README.md +94 -0
- package/plugins/computer_use/app/_template.md +142 -0
- package/plugins/computer_use/app/chrome.md +165 -0
- package/plugins/computer_use/app/finder.md +76 -0
- package/plugins/computer_use/app/macos-system-settings.md +81 -0
- package/plugins/computer_use/app/microsoft-excel.md +80 -0
- package/plugins/computer_use/app/microsoft-powerpoint.md +80 -0
- package/plugins/computer_use/app/microsoft-word.md +79 -0
- package/plugins/computer_use/app/wechat.md +413 -0
- package/plugins/computer_use/app/weread.md +319 -0
- package/plugins/computer_use/commands/computeruse.md +25 -0
- package/plugins/computer_use/commands/cu.md +9 -0
- package/plugins/computer_use/plugin.js +26 -0
- package/plugins/computer_use/plugin.ts +30 -0
- package/plugins/computer_use/src/capture.js +90 -0
- package/plugins/computer_use/src/capture.ts +104 -0
- package/plugins/computer_use/src/commands.js +91 -0
- package/plugins/computer_use/src/commands.ts +103 -0
- package/plugins/computer_use/src/daemon.js +134 -0
- package/plugins/computer_use/src/daemon.ts +161 -0
- package/plugins/computer_use/src/manuals.js +135 -0
- package/plugins/computer_use/src/manuals.ts +159 -0
- package/plugins/computer_use/src/verify.js +168 -0
- package/plugins/computer_use/src/verify.ts +219 -0
- package/plugins/computer_use/test/commands.test.ts +73 -0
- package/plugins/cron-runner/plugin.ts +38 -0
- package/plugins/cron-runner/src/commands.ts +373 -0
- package/plugins/cron-runner/src/cronTime.ts +155 -0
- package/plugins/cron-runner/src/lock.ts +79 -0
- package/plugins/cron-runner/src/runs.ts +99 -0
- package/plugins/cron-runner/src/scheduleEngine.ts +76 -0
- package/plugins/cron-runner/src/scheduler.ts +376 -0
- package/plugins/cron-runner/src/schema.ts +139 -0
- package/plugins/cron-runner/src/service.ts +193 -0
- package/plugins/cron-runner/src/store.ts +333 -0
- package/plugins/cron-runner/src/types.ts +141 -0
- package/plugins/cron-runner/test/cronTime.test.ts +41 -0
- package/plugins/cron-runner/test/plugin-integration.test.ts +47 -0
- package/plugins/cron-runner/test/scheduleEngine.test.ts +41 -0
- package/plugins/cron-runner/test/service.test.ts +76 -0
- package/plugins/cron-runner/test/store-and-scheduler.test.ts +501 -0
- package/plugins/workflow-runner/plugin.js +56 -2
- package/plugins/workflow-runner/plugin.ts +113 -0
- package/plugins/workflow-runner/src/agentCommon.js +43 -4
- package/plugins/workflow-runner/src/agentCommon.ts +435 -0
- package/plugins/workflow-runner/src/controlFlow/branch.js +40 -0
- package/plugins/workflow-runner/src/controlFlow/branch.ts +46 -0
- package/plugins/workflow-runner/src/controlFlow/loop.js +211 -0
- package/plugins/workflow-runner/src/controlFlow/loop.ts +248 -0
- package/plugins/workflow-runner/src/controlFlow/pause.js +129 -0
- package/plugins/workflow-runner/src/controlFlow/pause.ts +141 -0
- package/plugins/workflow-runner/src/controlFlow/signal.js +21 -0
- package/plugins/workflow-runner/src/controlFlow/signal.ts +29 -0
- package/plugins/workflow-runner/src/controlFlow/types.js +18 -0
- package/plugins/workflow-runner/src/controlFlow/types.ts +63 -0
- package/plugins/workflow-runner/src/dslManifest.ts +127 -0
- package/plugins/workflow-runner/src/expressions.ts +622 -0
- package/plugins/workflow-runner/src/index.js +13 -4
- package/plugins/workflow-runner/src/index.ts +338 -0
- package/plugins/workflow-runner/src/jsonPayload.ts +84 -0
- package/plugins/workflow-runner/src/loader.js +23 -0
- package/plugins/workflow-runner/src/loader.ts +721 -0
- package/plugins/workflow-runner/src/multimodal.js +2 -1
- package/plugins/workflow-runner/src/multimodal.ts +64 -0
- package/plugins/workflow-runner/src/recovery.ts +149 -0
- package/plugins/workflow-runner/src/runner.js +75 -659
- package/plugins/workflow-runner/src/runner.ts +604 -0
- package/plugins/workflow-runner/src/stepExecutors/agent.ts +234 -0
- package/plugins/workflow-runner/src/stepExecutors/assert.ts +50 -0
- package/plugins/workflow-runner/src/stepExecutors/echo.js +31 -0
- package/plugins/workflow-runner/src/stepExecutors/echo.ts +38 -0
- package/plugins/workflow-runner/src/stepExecutors/llm.ts +133 -0
- package/plugins/workflow-runner/src/stepExecutors/set.js +49 -0
- package/plugins/workflow-runner/src/stepExecutors/set.ts +56 -0
- package/plugins/workflow-runner/src/stepExecutors/skill.js +5 -6
- package/plugins/workflow-runner/src/stepExecutors/skill.ts +140 -0
- package/plugins/workflow-runner/src/stepExecutors/tool.js +80 -8
- package/plugins/workflow-runner/src/stepExecutors/tool.ts +256 -0
- package/plugins/workflow-runner/src/stepExecutors/verify.ts +68 -0
- package/plugins/workflow-runner/src/stepPipeline.js +277 -0
- package/plugins/workflow-runner/src/stepPipeline.ts +366 -0
- package/plugins/workflow-runner/src/taskIdentity.ts +405 -0
- package/plugins/workflow-runner/src/toolRuntime.js +48 -0
- package/plugins/workflow-runner/src/toolRuntime.ts +64 -0
- package/plugins/workflow-runner/src/types.js +37 -1
- package/plugins/workflow-runner/src/types.ts +602 -0
- package/plugins/workflow-runner/src/workflowState.ts +733 -0
- package/plugins/workflow-runner/test/agent.test.ts +587 -0
- package/plugins/workflow-runner/test/agentLlmError.test.ts +373 -0
- package/plugins/workflow-runner/test/bashBlacklist.sweep.test.ts +65 -0
- package/plugins/workflow-runner/test/captureMisbind.test.ts +99 -0
- package/plugins/workflow-runner/test/cli.e2e.test.ts +147 -0
- package/plugins/workflow-runner/test/context-files.test.ts +320 -0
- package/plugins/workflow-runner/test/e2e.test.ts +370 -0
- package/plugins/workflow-runner/test/engineRobustness.test.ts +226 -0
- package/plugins/workflow-runner/test/expressions.test.ts +397 -0
- package/plugins/workflow-runner/test/foundation.test.ts +126 -0
- package/plugins/workflow-runner/test/graceful.test.ts +139 -0
- package/plugins/workflow-runner/test/greedyInputs.load.test.ts +47 -0
- package/plugins/workflow-runner/test/hostCapability.test.ts +108 -0
- package/plugins/workflow-runner/test/illustrate.load.test.ts +183 -0
- package/plugins/workflow-runner/test/jsonPayload.test.ts +83 -0
- package/plugins/workflow-runner/test/loader.test.ts +617 -0
- package/plugins/workflow-runner/test/maxTurnsRetryDelay.test.ts +33 -0
- package/plugins/workflow-runner/test/multimodal.test.ts +342 -0
- package/plugins/workflow-runner/test/output-schema.test.ts +213 -0
- package/plugins/workflow-runner/test/parseWorkflowArgs.test.ts +87 -0
- package/plugins/workflow-runner/test/pluginRunner.isolation.test.ts +269 -0
- package/plugins/workflow-runner/test/ppt.load.test.ts +1220 -0
- package/plugins/workflow-runner/test/ralphLoop.e2e.test.ts +220 -0
- package/plugins/workflow-runner/test/recovery.test.ts +265 -0
- package/plugins/workflow-runner/test/runner.test.ts +911 -0
- package/plugins/workflow-runner/test/runnerPreflight.test.ts +379 -0
- package/plugins/workflow-runner/test/skillTurnDone.test.ts +71 -0
- package/plugins/workflow-runner/test/stateLifecycle.test.ts +150 -0
- package/plugins/workflow-runner/test/stepPipeline.test.ts +114 -0
- package/plugins/workflow-runner/test/taskArbitration.test.ts +338 -0
- package/plugins/workflow-runner/test/taskIdentity.test.ts +237 -0
- package/plugins/workflow-runner/test/taskLifecycle.test.ts +270 -0
- package/plugins/workflow-runner/test/toolArtifactOverwrite.test.ts +221 -0
- package/plugins/workflow-runner/test/toolCapabilityBoundary.test.ts +29 -0
- package/plugins/workflow-runner/test/toolRuntimeIntegration.test.ts +168 -0
- package/plugins/workflow-runner/test/webppt.load.test.ts +250 -0
- package/plugins/workflow-runner/test/word.load.test.ts +208 -0
- package/plugins/workflow-runner/test/workflows.smoke.test.ts +76 -0
- package/plugins/workflow-runner/test/yamlGuardConditions.test.ts +207 -0
- package/skills/browser-automation/SKILL.md +9 -19
- package/skills/web-crawler/SKILL.md +9 -11
- package/src/askBridge.ts +249 -0
- package/src/bootstrap/cwdArg.js +10 -3
- package/src/bootstrap/cwdArg.ts +31 -0
- package/src/bootstrap/workingDir.ts +48 -0
- package/src/cli/args.js +70 -7
- package/src/cli/args.ts +146 -0
- package/src/cli/computerUse.js +185 -0
- package/src/cli/computerUse.ts +221 -0
- package/src/cli/configMenu.js +274 -202
- package/src/cli/configMenu.tsx +521 -0
- package/src/cli/memoryAccess.js +45 -0
- package/src/cli/memoryAccess.ts +53 -0
- package/src/cli/print.js +119 -5
- package/src/cli/print.ts +597 -0
- package/src/cli/quickModelSetup.js +60 -0
- package/src/cli/quickModelSetup.tsx +96 -0
- package/src/cli/streamJson.js +80 -4
- package/src/cli/streamJson.ts +433 -0
- package/src/cli/wizards/advisorWizard.js +1 -0
- package/src/cli/wizards/advisorWizard.tsx +195 -0
- package/src/cli/wizards/agentRuntimeWizard.js +84 -0
- package/src/cli/wizards/agentRuntimeWizard.tsx +134 -0
- package/src/cli/wizards/browserWizard.js +55 -0
- package/src/cli/wizards/browserWizard.tsx +77 -0
- package/src/cli/wizards/contextAdvisorWizard.js +84 -0
- package/src/cli/wizards/contextAdvisorWizard.tsx +124 -0
- package/src/cli/wizards/fieldForm.js +7 -4
- package/src/cli/wizards/fieldForm.tsx +250 -0
- package/src/cli/wizards/imageWizard.js +4 -5
- package/src/cli/wizards/imageWizard.tsx +96 -0
- package/src/cli/wizards/openRouterWizard.tsx +48 -0
- package/src/cli/wizards/providerPresets.js +1 -8
- package/src/cli/wizards/providerPresets.ts +106 -0
- package/src/cli/wizards/providerWizard.js +152 -205
- package/src/cli/wizards/providerWizard.tsx +266 -0
- package/src/cli/wizards/searchWizard.tsx +47 -0
- package/src/cli/wizards/ttsWizard.js +4 -3
- package/src/cli/wizards/ttsWizard.tsx +61 -0
- package/src/config/configDefaults.js +25 -0
- package/src/config/configDefaults.ts +33 -0
- package/src/config/configDiagnostics.js +117 -0
- package/src/config/configDiagnostics.ts +166 -0
- package/src/config/configFile.js +52 -32
- package/src/config/configFile.ts +633 -0
- package/src/config/configManagement.js +126 -0
- package/src/config/configManagement.ts +159 -0
- package/src/config/configService.js +66 -11
- package/src/config/configService.ts +554 -0
- package/src/config/numericLimits.js +61 -0
- package/src/config/numericLimits.ts +80 -0
- package/src/config.ts +109 -0
- package/src/context/agentHome.ts +11 -0
- package/src/context/archive.ts +144 -0
- package/src/context/budgetPlan.js +54 -0
- package/src/context/budgetPlan.ts +88 -0
- package/src/context/compact.js +179 -106
- package/src/context/compact.ts +584 -0
- package/src/context/contextBundle.js +119 -0
- package/src/context/contextBundle.ts +176 -0
- package/src/context/contextLock.ts +84 -0
- package/src/context/microCompactLite.ts +218 -0
- package/src/context/persistContext.ts +177 -0
- package/src/context/reactiveCompact.js +19 -4
- package/src/context/reactiveCompact.ts +240 -0
- package/src/context/recentDirs.ts +42 -0
- package/src/context/sessionPath.ts +206 -0
- package/src/context/sessionRegistry.js +8 -2
- package/src/context/sessionRegistry.ts +414 -0
- package/src/context/snipCompact.ts +163 -0
- package/src/context/tokenCounter.ts +106 -0
- package/src/context/workspaceIndex.js +2 -2
- package/src/context/workspaceIndex.ts +751 -0
- package/src/entry.compiled.tsx +20 -0
- package/src/harness/claimValidation.js +9 -0
- package/src/harness/claimValidation.ts +22 -0
- package/src/harness/evidenceLedger.js +92 -0
- package/src/harness/evidenceLedger.ts +96 -0
- package/src/harness/stallGuard.js +60 -0
- package/src/harness/stallGuard.ts +81 -0
- package/src/humanIntervention.js +129 -0
- package/src/humanIntervention.ts +216 -0
- package/src/infra/verification.ts +295 -0
- package/src/irreversible/rules.js +216 -0
- package/src/irreversible/rules.ts +261 -0
- package/src/llm/client.js +40 -3
- package/src/llm/client.ts +621 -0
- package/src/llm/promptCache.js +27 -0
- package/src/llm/promptCache.ts +41 -0
- package/src/llm/providers/anthropicFormat.js +41 -5
- package/src/llm/providers/anthropicFormat.ts +290 -0
- package/src/llm/providers/openaiFormat.js +186 -8
- package/src/llm/providers/openaiFormat.ts +428 -0
- package/src/loop.js +614 -63
- package/src/loop.ts +1454 -0
- package/src/main.js +67 -10
- package/src/main.tsx +344 -0
- package/src/memory/codec.js +204 -0
- package/src/memory/codec.ts +249 -0
- package/src/memory/constants.js +34 -0
- package/src/memory/constants.ts +50 -0
- package/src/memory/episodicMemory.js +684 -0
- package/src/memory/episodicMemory.ts +815 -0
- package/src/memory/fileStore.js +148 -0
- package/src/memory/fileStore.ts +184 -0
- package/src/memory/memoryService.js +570 -0
- package/src/memory/memoryService.ts +859 -0
- package/src/memory/migration.js +128 -0
- package/src/memory/migration.ts +160 -0
- package/src/memory/repository.js +789 -0
- package/src/memory/repository.ts +893 -0
- package/src/memory/retrieval.js +108 -0
- package/src/memory/retrieval.ts +145 -0
- package/src/memory/searchIndex.js +107 -0
- package/src/memory/searchIndex.ts +153 -0
- package/src/memory/topicView.js +82 -0
- package/src/memory/topicView.ts +95 -0
- package/src/memory/turnJournal.js +95 -0
- package/src/memory/turnJournal.ts +120 -0
- package/src/memory/types.js +2 -0
- package/src/memory/types.ts +186 -0
- package/src/messageQueue.ts +123 -0
- package/src/multimodal.js +128 -46
- package/src/multimodal.ts +349 -0
- package/src/plugin-sdk.js +21 -1
- package/src/plugin-sdk.ts +134 -0
- package/src/plugins/builtinPluginIds.js +47 -0
- package/src/plugins/builtinPluginIds.ts +51 -0
- package/src/plugins/builtinRegistry.ts +43 -0
- package/src/plugins/commandRouter.js +40 -26
- package/src/plugins/commandRouter.ts +261 -0
- package/src/plugins/hookEngine.ts +341 -0
- package/src/plugins/pluginApi.ts +133 -0
- package/src/plugins/pluginLifecycle.ts +34 -0
- package/src/plugins/pluginLoader.ts +147 -0
- package/src/plugins/pluginManifest.js +127 -0
- package/src/plugins/pluginManifest.ts +152 -0
- package/src/plugins/pluginRunner.js +47 -2
- package/src/plugins/pluginRunner.ts +207 -0
- package/src/plugins/transcript.ts +208 -0
- package/src/prompts/lint.js +169 -0
- package/src/prompts/lint.ts +220 -0
- package/src/prompts/pluginList.js +32 -16
- package/src/prompts/pluginList.ts +86 -0
- package/src/prompts/policyRegistry.js +318 -0
- package/src/prompts/policyRegistry.ts +358 -0
- package/src/prompts/projectInstructions.js +24 -0
- package/src/prompts/projectInstructions.ts +81 -0
- package/src/prompts/promptAssembly.js +311 -202
- package/src/prompts/promptAssembly.ts +510 -0
- package/src/prompts/skillList.js +106 -45
- package/src/prompts/skillList.ts +198 -0
- package/src/prompts/system.js +31 -6
- package/src/prompts/system.ts +81 -0
- package/src/runtime/agentRuntimeContext.js +53 -0
- package/src/runtime/agentRuntimeContext.ts +112 -0
- package/src/session/runAgentTurn.js +71 -12
- package/src/session/runAgentTurn.ts +181 -0
- package/src/session/runTurn.js +29 -0
- package/src/session/runTurn.ts +160 -0
- package/src/session/runtimeStore.js +149 -0
- package/src/session/runtimeStore.ts +197 -0
- package/src/session/sessionState.js +79 -3
- package/src/session/sessionState.ts +134 -0
- package/src/toolGate.ts +50 -0
- package/src/tools/advisor/advisor.js +76 -15
- package/src/tools/advisor/advisor.ts +512 -0
- package/src/tools/agent/agent.js +245 -29
- package/src/tools/agent/agent.ts +837 -0
- package/src/tools/agent/agentTaskService.js +60 -0
- package/src/tools/agent/agentTaskService.ts +113 -0
- package/src/tools/agent/agentTypes.js +20 -8
- package/src/tools/agent/agentTypes.ts +94 -0
- package/src/tools/agent/coordinator.ts +111 -0
- package/src/tools/agent/worktree.ts +296 -0
- package/src/tools/ask/ask.js +17 -0
- package/src/tools/ask/ask.ts +122 -0
- package/src/tools/bash/attachImage.js +223 -0
- package/src/tools/bash/attachImage.ts +281 -0
- package/src/tools/bash/bash.js +132 -14
- package/src/tools/bash/bash.ts +642 -0
- package/src/tools/bash/semantics.ts +103 -0
- package/src/tools/bash/warnings.js +17 -11
- package/src/tools/bash/warnings.ts +117 -0
- package/src/tools/capabilities.js +42 -0
- package/src/tools/capabilities.ts +69 -0
- package/src/tools/computeruse/accessibilityPermission.js +261 -0
- package/src/tools/computeruse/accessibilityPermission.ts +294 -0
- package/src/tools/computeruse/axSearch.js +96 -0
- package/src/tools/computeruse/axSearch.ts +120 -0
- package/src/tools/computeruse/axSnap.js +272 -0
- package/src/tools/computeruse/axSnap.ts +349 -0
- package/src/tools/computeruse/capabilityLabels.js +95 -0
- package/src/tools/computeruse/capabilityLabels.ts +115 -0
- package/src/tools/computeruse/computerUseService.js +2235 -0
- package/src/tools/computeruse/computerUseService.ts +2686 -0
- package/src/tools/computeruse/computeruse.js +2742 -0
- package/src/tools/computeruse/computeruse.ts +3265 -0
- package/src/tools/computeruse/diagnosticTrace.js +154 -0
- package/src/tools/computeruse/diagnosticTrace.ts +195 -0
- package/src/tools/computeruse/diagnostics.js +369 -0
- package/src/tools/computeruse/diagnostics.ts +453 -0
- package/src/tools/computeruse/grants.js +53 -0
- package/src/tools/computeruse/grants.ts +70 -0
- package/src/tools/computeruse/humanBoundary.js +159 -0
- package/src/tools/computeruse/humanBoundary.ts +199 -0
- package/src/tools/computeruse/intervention.js +77 -0
- package/src/tools/computeruse/intervention.ts +110 -0
- package/src/tools/computeruse/manualSchema.js +709 -0
- package/src/tools/computeruse/manualSchema.ts +916 -0
- package/src/tools/computeruse/observationImages.js +705 -0
- package/src/tools/computeruse/observationImages.ts +889 -0
- package/src/tools/computeruse/protocol.js +60 -0
- package/src/tools/computeruse/protocol.ts +515 -0
- package/src/tools/computeruse/telemetryLog.js +57 -0
- package/src/tools/computeruse/telemetryLog.ts +67 -0
- package/src/tools/computeruse/vision/appGuide.js +325 -0
- package/src/tools/computeruse/vision/appGuide.ts +482 -0
- package/src/tools/computeruse/vision/focus.js +291 -0
- package/src/tools/computeruse/vision/focus.ts +382 -0
- package/src/tools/computeruse/vision/frames.js +248 -0
- package/src/tools/computeruse/vision/frames.ts +329 -0
- package/src/tools/computeruse/vision/geometry.js +131 -0
- package/src/tools/computeruse/vision/geometry.ts +163 -0
- package/src/tools/computeruse/vision/imaging.js +201 -0
- package/src/tools/computeruse/vision/imaging.ts +281 -0
- package/src/tools/computeruse/vision/png.js +274 -0
- package/src/tools/computeruse/vision/png.ts +317 -0
- package/src/tools/computeruse/vision/raster.js +320 -0
- package/src/tools/computeruse/vision/raster.ts +408 -0
- package/src/tools/computeruse/vision/settle.js +131 -0
- package/src/tools/computeruse/vision/settle.ts +220 -0
- package/src/tools/computeruse/vision/telemetry.js +196 -0
- package/src/tools/computeruse/vision/telemetry.ts +241 -0
- package/src/tools/computeruse/visionOcr.js +115 -0
- package/src/tools/computeruse/visionOcr.ts +128 -0
- package/src/tools/computeruse/waitFor.js +237 -0
- package/src/tools/computeruse/waitFor.ts +291 -0
- package/src/tools/download/download.js +10 -6
- package/src/tools/download/download.ts +187 -0
- package/src/tools/edit/edit.js +30 -18
- package/src/tools/edit/edit.ts +349 -0
- package/src/tools/edit/multi-edit.js +25 -14
- package/src/tools/edit/multi-edit.ts +223 -0
- package/src/tools/effectiveToolSet.js +73 -0
- package/src/tools/effectiveToolSet.ts +91 -0
- package/src/tools/genimage/genimage.ts +257 -0
- package/src/tools/genspeech/genspeech.ts +83 -0
- package/src/tools/glob/glob.js +176 -29
- package/src/tools/glob/glob.ts +283 -0
- package/src/tools/grep/grep.js +61 -19
- package/src/tools/grep/grep.ts +239 -0
- package/src/tools/grep/rgPath.ts +206 -0
- package/src/tools/index.js +99 -5
- package/src/tools/index.ts +293 -0
- package/src/tools/lsp/config.js +113 -0
- package/src/tools/lsp/config.ts +113 -0
- package/src/tools/lsp/lsp.js +256 -0
- package/src/tools/lsp/lsp.ts +274 -0
- package/src/tools/lsp/service.js +303 -0
- package/src/tools/lsp/service.ts +296 -0
- package/src/tools/memory/memory.js +520 -0
- package/src/tools/memory/memory.ts +625 -0
- package/src/tools/memory/policy.js +53 -0
- package/src/tools/memory/policy.ts +72 -0
- package/src/tools/notebook/notebookEdit.js +114 -0
- package/src/tools/notebook/notebookEdit.ts +126 -0
- package/src/tools/powershell/powershell.js +210 -0
- package/src/tools/powershell/powershell.ts +239 -0
- package/src/tools/presentresult/presentresult.js +145 -0
- package/src/tools/presentresult/presentresult.ts +156 -0
- package/src/tools/read/read.js +371 -163
- package/src/tools/read/read.ts +558 -0
- package/src/tools/shared/diffSummary.js +44 -0
- package/src/tools/shared/diffSummary.ts +63 -0
- package/src/tools/shared/fileActivity.js +51 -0
- package/src/tools/shared/fileActivity.ts +66 -0
- package/src/tools/shared/fileMutationBus.js +13 -0
- package/src/tools/shared/fileMutationBus.ts +14 -0
- package/src/tools/shared/fileState.js +73 -65
- package/src/tools/shared/fileState.ts +145 -0
- package/src/tools/shared/fileUtils.js +81 -4
- package/src/tools/shared/fileUtils.ts +504 -0
- package/src/tools/shared/killTree.ts +50 -0
- package/src/tools/shared/permanentDeleteGuard.js +26 -0
- package/src/tools/shared/permanentDeleteGuard.ts +34 -0
- package/src/tools/shared/schemas.ts +20 -0
- package/src/tools/shared/toolOutputStore.js +97 -0
- package/src/tools/shared/toolOutputStore.ts +110 -0
- package/src/tools/skill/skill.js +86 -0
- package/src/tools/skill/skill.ts +94 -0
- package/src/tools/snip/snip.js +67 -0
- package/src/tools/snip/snip.ts +80 -0
- package/src/tools/tasks/backgroundTaskService.js +275 -0
- package/src/tools/tasks/backgroundTaskService.ts +369 -0
- package/src/tools/tasks/processEvidence.js +24 -0
- package/src/tools/tasks/processEvidence.ts +34 -0
- package/src/tools/tasks/taskManagement.js +163 -0
- package/src/tools/tasks/taskManagement.ts +180 -0
- package/src/tools/tasks/taskOutput.js +113 -0
- package/src/tools/tasks/taskOutput.ts +145 -0
- package/src/tools/tasks/taskStop.js +74 -0
- package/src/tools/tasks/taskStop.ts +99 -0
- package/src/tools/tasks/taskStore.js +46 -0
- package/src/tools/tasks/taskStore.ts +77 -0
- package/src/tools/terminal/terminalCapture.js +167 -0
- package/src/tools/terminal/terminalCapture.ts +179 -0
- package/src/tools/trash/trash.js +233 -0
- package/src/tools/trash/trash.ts +283 -0
- package/src/tools/types.js +1 -1
- package/src/tools/types.ts +24 -0
- package/src/tools/webbrowser/agentBrowserPath.ts +286 -0
- package/src/tools/webbrowser/browser.js +214 -11
- package/src/tools/webbrowser/browser.ts +293 -0
- package/src/tools/webbrowser/browserControl.js +153 -48
- package/src/tools/webbrowser/browserControl.ts +262 -0
- package/src/tools/webbrowser/browserDaemon.js +147 -39
- package/src/tools/webbrowser/browserDaemon.ts +270 -0
- package/src/tools/webbrowser/browserOutcome.js +273 -0
- package/src/tools/webbrowser/browserOutcome.ts +306 -0
- package/src/tools/webbrowser/browserRunRegistry.js +150 -0
- package/src/tools/webbrowser/browserRunRegistry.ts +179 -0
- package/src/tools/webbrowser/browserService.js +20 -6
- package/src/tools/webbrowser/browserService.ts +56 -0
- package/src/tools/webbrowser/browserStream.js +30 -1
- package/src/tools/webbrowser/browserStream.ts +216 -0
- package/src/tools/webbrowser/browserTabs.js +143 -0
- package/src/tools/webbrowser/browserTabs.ts +210 -0
- package/src/tools/webbrowser/webbrowser.js +831 -150
- package/src/tools/webbrowser/webbrowser.ts +1537 -0
- package/src/tools/webfetch/preapproved.ts +274 -0
- package/src/tools/webfetch/webfetch.js +24 -23
- package/src/tools/webfetch/webfetch.ts +383 -0
- package/src/tools/websearch/websearch.js +12 -11
- package/src/tools/websearch/websearch.ts +292 -0
- package/src/tools/worktree/sessionWorktree.js +128 -0
- package/src/tools/worktree/sessionWorktree.ts +158 -0
- package/src/tools/write/write.js +27 -44
- package/src/tools/write/write.ts +136 -0
- package/src/types.ts +960 -0
- package/src/ui/App.js +216 -12
- package/src/ui/App.tsx +879 -0
- package/src/ui/AskQuestion.tsx +300 -0
- package/src/ui/InputBox.js +77 -1
- package/src/ui/InputBox.tsx +567 -0
- package/src/ui/LiveArea.tsx +344 -0
- package/src/ui/MessageList.js +27 -6
- package/src/ui/MessageList.tsx +559 -0
- package/src/ui/ModelPicker.tsx +107 -0
- package/src/ui/QueuedMessagesBanner.tsx +51 -0
- package/src/ui/Root.js +6 -8
- package/src/ui/Root.tsx +100 -0
- package/src/ui/SessionPicker.tsx +169 -0
- package/src/ui/StatusLine.js +22 -1
- package/src/ui/StatusLine.tsx +145 -0
- package/src/ui/WorkspacePicker.tsx +84 -0
- package/src/ui/advisorCommand.js +54 -0
- package/src/ui/advisorCommand.ts +63 -0
- package/src/ui/computerCommand.js +19 -0
- package/src/ui/computerCommand.ts +35 -0
- package/src/ui/figures.ts +45 -0
- package/src/ui/hooks/askSlot.ts +112 -0
- package/src/ui/hooks/useChat.js +271 -7
- package/src/ui/hooks/useChat.ts +1578 -0
- package/src/ui/hooks/useInputHistory.ts +235 -0
- package/src/ui/hooks/usePasteHandler.ts +220 -0
- package/src/ui/hooks/useTerminalSize.ts +41 -0
- package/src/ui/hooks/useTextBuffer.ts +108 -0
- package/src/ui/hooks/useTokenUsage.ts +44 -0
- package/src/ui/liveViewport.ts +189 -0
- package/src/ui/markdown.ts +230 -0
- package/src/ui/textBuffer.ts +239 -0
- package/src/ui/theme.ts +35 -0
- package/src/utils/clipboardImage.js +358 -0
- package/src/utils/clipboardImage.ts +420 -0
- package/src/utils/debugLog.ts +50 -0
- package/src/utils/greenRoot.ts +35 -0
- package/src/utils/packageRoot.ts +40 -0
- package/src/utils/pathContains.js +28 -0
- package/src/utils/pathContains.ts +31 -0
- package/src/utils/resourcePaths.ts +71 -0
- package/src/utils/stripThink.ts +49 -0
- package/src/utils/zodToJson.ts +73 -0
- package/src/web/browserBridge.js +365 -66
- package/src/web/browserBridge.ts +672 -0
- package/src/web/protocol.ts +1804 -0
- package/vendor/computer-use/arm64-darwin/cu-helperd +0 -0
- package/workflows/ppt.yaml +13 -2
- package/workflows/wechat-reading-shots.yaml +315 -0
- package/workflows/word.yaml +11 -1
- package/skills/docx/scripts/office/helpers/__pycache__/__init__.cpython-314.pyc +0 -0
- package/skills/docx/scripts/office/helpers/__pycache__/merge_runs.cpython-314.pyc +0 -0
- package/skills/docx/scripts/office/helpers/__pycache__/simplify_redlines.cpython-314.pyc +0 -0
- package/skills/docx/scripts/office/validators/__pycache__/__init__.cpython-314.pyc +0 -0
- package/skills/docx/scripts/office/validators/__pycache__/base.cpython-314.pyc +0 -0
- package/skills/docx/scripts/office/validators/__pycache__/docx.cpython-314.pyc +0 -0
- package/skills/docx/scripts/office/validators/__pycache__/pptx.cpython-314.pyc +0 -0
- package/skills/docx/scripts/office/validators/__pycache__/redlining.cpython-314.pyc +0 -0
- package/skills/image-stats/scripts/__pycache__/image_stats.cpython-314.pyc +0 -0
- package/skills/image-stats/scripts/__pycache__/image_stats_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/assemble_material.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/assemble_material_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/chart_sanity_check.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/check_language_policy.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/check_ui_artifact.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/check_ui_artifact_pptx_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/deck_pages_check.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/deck_pages_check_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/deck_visual_style_check.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/deck_visual_style_check_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/design_budget_check.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/design_budget_check_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/design_repair_hints.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/design_repair_hints_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/design_style_check.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/design_style_check_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/draft_coverage_check.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/extract_source_assets.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/extract_source_assets_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/generated_image_review_collect.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/generated_image_review_collect_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/image_plan_lint.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/image_plan_lint_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/image_prompt_rewrite.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/image_prompt_rewrite_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/layout_balance.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/layout_balance_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/normalize_design_json.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/normalize_design_json_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/plan_source_layout.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/plan_source_layout_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/resolve_source_hint.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/resolve_source_hint_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/resolve_source_input.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/resolve_source_input_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/scaffold_ui_artifact.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/source_usage_check.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/source_usage_check_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/text_readability_pixel.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/visual_quality_gate.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/visual_quality_gate_test.cpython-314.pyc +0 -0
- /package/skills/ai-image/scripts/{generate_test.mjs → generate.selftest.mjs} +0 -0
- /package/skills/ai-image/scripts/{generate_from_plan_test.mjs → generate_from_plan.selftest.mjs} +0 -0
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { dirname, isAbsolute } from 'node:path';
|
|
3
|
+
|
|
4
|
+
import { resolveAgentBrowserPath, resolveBundledChromium, resolveSystemBrowser } from './agentBrowserPath.js';
|
|
5
|
+
import {
|
|
6
|
+
browserGlobalArgs,
|
|
7
|
+
runBounded,
|
|
8
|
+
sessionArgs,
|
|
9
|
+
spawnRunner,
|
|
10
|
+
type AgentBrowserRunner,
|
|
11
|
+
} from './browser.js';
|
|
12
|
+
|
|
13
|
+
export interface BrowserDaemonInfo {
|
|
14
|
+
sessionName: string;
|
|
15
|
+
streamPort: number;
|
|
16
|
+
pid?: number;
|
|
17
|
+
alreadyRunning: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Daemon 启动(拉起 Chrome + CDP 就绪)总预算:超时即掐断并抛可操作错误,绝不无限挂。 */
|
|
21
|
+
export const DAEMON_BRINGUP_TIMEOUT_MS = 45_000;
|
|
22
|
+
/** 单次 `stream status` 探测预算:daemon socket 卡死时不拖累调用方。 */
|
|
23
|
+
export const STATUS_TIMEOUT_MS = 8_000;
|
|
24
|
+
|
|
25
|
+
const ports = new Map<string, number>();
|
|
26
|
+
const starts = new Map<string, Promise<BrowserDaemonInfo>>();
|
|
27
|
+
|
|
28
|
+
/** Daemon 生命周期可选项:透传父级中断 signal + 覆盖启动预算 + 回收注入(测试用)。 */
|
|
29
|
+
export interface EnsureDaemonOptions {
|
|
30
|
+
signal?: AbortSignal;
|
|
31
|
+
timeoutMs?: number;
|
|
32
|
+
reclaim?: ReclaimDeps;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Daemon 子进程执行缝(默认走真实实现):单测注入假 runner/resolveBin 即可确定性覆盖
|
|
37
|
+
* 「启动有界/可中断」等分支,不碰真 Chrome、不用 mock.module(见项目测试约定)。
|
|
38
|
+
*/
|
|
39
|
+
export interface DaemonDeps {
|
|
40
|
+
resolveBin: () => Promise<string | null>;
|
|
41
|
+
runner: AgentBrowserRunner;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const realDaemonDeps: DaemonDeps = {
|
|
45
|
+
resolveBin: resolveAgentBrowserPath,
|
|
46
|
+
runner: spawnRunner,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/** 回收残留进程用的杀进程执行器(默认真实 spawn,等它结束再 resolve;单测注入假实现断言作用域)。 */
|
|
50
|
+
export type ReclaimExec = (file: string, args: string[]) => Promise<void>;
|
|
51
|
+
|
|
52
|
+
const realReclaimExec: ReclaimExec = (file, args) =>
|
|
53
|
+
new Promise<void>((resolve) => {
|
|
54
|
+
try {
|
|
55
|
+
const child = spawn(file, args, { stdio: 'ignore', windowsHide: true });
|
|
56
|
+
child.on('close', () => resolve());
|
|
57
|
+
child.on('error', () => resolve());
|
|
58
|
+
} catch {
|
|
59
|
+
resolve();
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
export interface ReclaimDeps {
|
|
64
|
+
binDir?: () => Promise<string | null>;
|
|
65
|
+
platform?: NodeJS.Platform;
|
|
66
|
+
exec?: ReclaimExec;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* 回收「本仓库/本 App 的」残留 agent-browser daemon + 其 Chrome 子树(Windows `taskkill /F /T`)。
|
|
71
|
+
*
|
|
72
|
+
* 根因:agent-browser 的 daemon 是「黏」的后台进程,`--profile`/`--executable-path` 等 launch-only
|
|
73
|
+
* flag 首次启动即固化、之后忽略(见 readStatus 头注)。一次无 profile 的启动就毒化整个 session,
|
|
74
|
+
* 之后复用继承临时 profile、重启 App 也不解,且孤儿 Chrome 堆积。宿主(Desktop runtime)在启动时
|
|
75
|
+
* 调用一次本函数,把上一轮残留清干净,下次 bring-up 便以我们的持久 `--profile` 干净重启。
|
|
76
|
+
*
|
|
77
|
+
* 安全:**仅**按「已解析二进制所在目录前缀」作用域匹配 agent-browser.exe,绝不误伤安装版桌面 App
|
|
78
|
+
* 或系统上别处的 agent-browser 用法(对标 scripts/dev-browser.ts::reclaimStalePort)。无法作用域限定
|
|
79
|
+
* (PATH 上裸二进制 / 没装)时保守放弃,不做全局杀。幂等、best-effort(失败静默)。
|
|
80
|
+
*/
|
|
81
|
+
export async function reclaimStaleDaemons(deps: ReclaimDeps = {}): Promise<void> {
|
|
82
|
+
const platform = deps.platform ?? process.platform;
|
|
83
|
+
const exec = deps.exec ?? realReclaimExec;
|
|
84
|
+
const resolveDir =
|
|
85
|
+
deps.binDir ??
|
|
86
|
+
(async () => {
|
|
87
|
+
const bin = await resolveAgentBrowserPath();
|
|
88
|
+
return bin && isAbsolute(bin) ? dirname(bin) : null;
|
|
89
|
+
});
|
|
90
|
+
const dir = await resolveDir();
|
|
91
|
+
if (!dir) return;
|
|
92
|
+
ports.clear();
|
|
93
|
+
if (platform === 'win32') {
|
|
94
|
+
const root = dir.toLowerCase().replace(/'/g, "''");
|
|
95
|
+
const ps =
|
|
96
|
+
// (a) 本 App 路径下的 agent-browser daemon + 其 Chrome 子树。
|
|
97
|
+
`Get-CimInstance Win32_Process -Filter "Name='agent-browser.exe'" | ` +
|
|
98
|
+
`Where-Object { $_.ExecutablePath -and $_.ExecutablePath.ToLower().StartsWith('${root}') } | ` +
|
|
99
|
+
`ForEach-Object { taskkill /F /T /PID $_.ProcessId 2>$null | Out-Null }; ` +
|
|
100
|
+
// (b) 真孤儿 agent-browser Chrome(父进程已死)。daemon 崩溃后残留、上面的 agent-browser 树
|
|
101
|
+
// 够不着,堆积起来会把新 daemon 的 Chrome 拉起拖到几十秒(实测 18 棵孤儿 → 卡 43s;
|
|
102
|
+
// 清空后同一启动仅 1s)。**只杀父进程已不在的孤儿**,绝不误伤有活父进程的正常浏览器
|
|
103
|
+
// (安装版 App 的活浏览器父 agent-browser 健在 → 不匹配,天然豁免)。
|
|
104
|
+
`$alive=@{}; Get-CimInstance Win32_Process | ForEach-Object { $alive[[int]$_.ProcessId]=$true }; ` +
|
|
105
|
+
`Get-CimInstance Win32_Process -Filter "Name='chrome.exe'" | ` +
|
|
106
|
+
`Where-Object { ($_.CommandLine -match 'agent-browser-chrome' -or $_.CommandLine -match '\\.minimal-agent.{0,40}profiles') -and -not $alive.ContainsKey([int]$_.ParentProcessId) } | ` +
|
|
107
|
+
`ForEach-Object { taskkill /F /T /PID $_.ProcessId 2>$null | Out-Null }`;
|
|
108
|
+
await exec('powershell', ['-NoProfile', '-NonInteractive', '-Command', ps]);
|
|
109
|
+
} else {
|
|
110
|
+
// unix:按可执行路径前缀 pkill agent-browser(其 Chrome 子进程随之收尾)。unix 孤儿会被
|
|
111
|
+
// reparent 到 init,无「父已死」信号,故此处不做等价的孤儿 Chrome 清扫(Windows 才是主战场)。
|
|
112
|
+
await exec('pkill', ['-f', `${dir}/agent-browser`]);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function parsePort(text: string): number | null {
|
|
117
|
+
const match = text.match(/ws:\/\/(?:127\.0\.0\.1|localhost):([0-9]+)/i);
|
|
118
|
+
if (!match) return null;
|
|
119
|
+
const port = Number(match[1]);
|
|
120
|
+
return Number.isInteger(port) && port > 0 && port < 65_536 ? port : null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function browserRuntimeMissing(text: string): boolean {
|
|
124
|
+
return /browser\s+(?:not found|was not found)|chrom(?:e|ium).*(?:not found|missing)|executable.*(?:not found|does not exist)|run ['\"]?agent-browser install/i.test(text);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export { browserRuntimeMissing as _browserRuntimeMissing };
|
|
128
|
+
|
|
129
|
+
async function readStatus(
|
|
130
|
+
bin: string,
|
|
131
|
+
sessionName: string,
|
|
132
|
+
opts: EnsureDaemonOptions = {},
|
|
133
|
+
deps: DaemonDeps = realDaemonDeps,
|
|
134
|
+
): Promise<{ port: number | null; connected: boolean }> {
|
|
135
|
+
// Profile/executable flags are launch-only. Repeating them against a running
|
|
136
|
+
// daemon makes agent-browser ignore the profile and emit misleading warnings.
|
|
137
|
+
// 有界:daemon socket 卡死时不把 status 探测拖成无限挂(getStreamPort/ensureDaemon 都靠它)。
|
|
138
|
+
const result = await runBounded(
|
|
139
|
+
bin,
|
|
140
|
+
[...sessionArgs(sessionName), 'stream', 'status'],
|
|
141
|
+
{ signal: opts.signal, timeoutMs: opts.timeoutMs ?? STATUS_TIMEOUT_MS },
|
|
142
|
+
deps.runner,
|
|
143
|
+
);
|
|
144
|
+
if (result.code !== 0) return { port: null, connected: false };
|
|
145
|
+
const text = `${result.stdout}\n${result.stderr}`;
|
|
146
|
+
return { port: parsePort(text), connected: /Connected:\s*true/i.test(text) };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export async function ensureDaemon(
|
|
150
|
+
sessionName = 'minimal-agent',
|
|
151
|
+
opts: EnsureDaemonOptions = {},
|
|
152
|
+
deps: DaemonDeps = realDaemonDeps,
|
|
153
|
+
): Promise<BrowserDaemonInfo> {
|
|
154
|
+
const bin = await deps.resolveBin();
|
|
155
|
+
if (!bin) throw new Error('未找到 agent-browser 可执行文件');
|
|
156
|
+
const status = await readStatus(bin, sessionName, { signal: opts.signal }, deps);
|
|
157
|
+
if (status.port && status.connected) {
|
|
158
|
+
ports.set(sessionName, status.port);
|
|
159
|
+
return { sessionName, streamPort: status.port, alreadyRunning: true };
|
|
160
|
+
}
|
|
161
|
+
// 半死 daemon(socket 端口在、但 Chrome 已亡 connected:false):对谁都没用,先回收再重启,
|
|
162
|
+
// 否则新 open 可能连到无 Chrome 的僵尸 daemon 上卡死。回收对健康的共享 daemon 无影响(那会
|
|
163
|
+
// 走上面的 connected 分支直接复用)。
|
|
164
|
+
if (status.port && !status.connected) {
|
|
165
|
+
await reclaimStaleDaemons(opts.reclaim);
|
|
166
|
+
}
|
|
167
|
+
const pending = starts.get(sessionName);
|
|
168
|
+
if (pending) return pending;
|
|
169
|
+
|
|
170
|
+
const budget = opts.timeoutMs ?? DAEMON_BRINGUP_TIMEOUT_MS;
|
|
171
|
+
const start = (async () => {
|
|
172
|
+
// Bring-up 边界:父级 signal abort 或超预算即掐断所有内部子进程。旧缺陷是 ensureDaemon
|
|
173
|
+
// 全程无 timeout、不接 signal —— Chrome/CDP 起不来时无限挂、用户 stop/ESC 也掐不断
|
|
174
|
+
// (「访问网站卡 5m53s」的根因)。这里统一一个控制器兜住整段启动。
|
|
175
|
+
const ac = new AbortController();
|
|
176
|
+
let timedOut = false;
|
|
177
|
+
const onParent = () => ac.abort(opts.signal?.reason);
|
|
178
|
+
const timer = setTimeout(() => {
|
|
179
|
+
timedOut = true;
|
|
180
|
+
ac.abort();
|
|
181
|
+
}, budget);
|
|
182
|
+
if (opts.signal) {
|
|
183
|
+
if (opts.signal.aborted) ac.abort(opts.signal.reason);
|
|
184
|
+
else opts.signal.addEventListener('abort', onParent, { once: true });
|
|
185
|
+
}
|
|
186
|
+
const bringUpError = () =>
|
|
187
|
+
new Error(
|
|
188
|
+
timedOut
|
|
189
|
+
? `内置浏览器启动超时:Chrome 未在 ${Math.round(budget / 1000)}s 内就绪(可能被残留浏览器进程占用)。可稍后重试,或改用 WebFetch 读取网页。`
|
|
190
|
+
: '内置浏览器启动已被中断。',
|
|
191
|
+
);
|
|
192
|
+
const run = (args: string[]) => deps.runner(bin, args, ac.signal);
|
|
193
|
+
try {
|
|
194
|
+
const globals = await browserGlobalArgs(sessionName);
|
|
195
|
+
// Prefer a stable system browser executable, but always use KakadeAI's
|
|
196
|
+
// isolated profile. Bundled Chromium remains an offline fallback.
|
|
197
|
+
const systemBrowser = resolveSystemBrowser();
|
|
198
|
+
let opened = await run([
|
|
199
|
+
...globals,
|
|
200
|
+
...(systemBrowser ? ['--executable-path', systemBrowser] : []),
|
|
201
|
+
'open',
|
|
202
|
+
'about:blank',
|
|
203
|
+
]);
|
|
204
|
+
if (ac.signal.aborted) throw bringUpError();
|
|
205
|
+
if (opened.code !== 0 && browserRuntimeMissing(`${opened.stderr}\n${opened.stdout}`)) {
|
|
206
|
+
const chromium = resolveBundledChromium();
|
|
207
|
+
if (chromium) {
|
|
208
|
+
opened = await run([...globals, '--executable-path', chromium, 'open', 'about:blank']);
|
|
209
|
+
} else {
|
|
210
|
+
const installed = await run(['install']);
|
|
211
|
+
if (installed.code === 0) opened = await run([...globals, 'open', 'about:blank']);
|
|
212
|
+
}
|
|
213
|
+
if (ac.signal.aborted) throw bringUpError();
|
|
214
|
+
}
|
|
215
|
+
if (opened.code !== 0) {
|
|
216
|
+
throw new Error((opened.stderr || opened.stdout || '浏览器启动失败').trim());
|
|
217
|
+
}
|
|
218
|
+
const streamPort = await waitForStreamPort(sessionName, 10_000, deps);
|
|
219
|
+
ports.set(sessionName, streamPort);
|
|
220
|
+
return { sessionName, streamPort, alreadyRunning: false };
|
|
221
|
+
} finally {
|
|
222
|
+
clearTimeout(timer);
|
|
223
|
+
opts.signal?.removeEventListener('abort', onParent);
|
|
224
|
+
}
|
|
225
|
+
})().finally(() => starts.delete(sessionName));
|
|
226
|
+
starts.set(sessionName, start);
|
|
227
|
+
return start;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export async function stopDaemon(
|
|
231
|
+
sessionName = 'minimal-agent',
|
|
232
|
+
deps: DaemonDeps = realDaemonDeps,
|
|
233
|
+
reclaim?: ReclaimDeps,
|
|
234
|
+
): Promise<void> {
|
|
235
|
+
const bin = await deps.resolveBin();
|
|
236
|
+
ports.delete(sessionName);
|
|
237
|
+
if (bin) {
|
|
238
|
+
await runBounded(bin, [...sessionArgs(sessionName), 'close'], { timeoutMs: STATUS_TIMEOUT_MS }, deps.runner);
|
|
239
|
+
}
|
|
240
|
+
// 优雅 close 之外再 tree-kill 一遍本 App 路径下的残留:崩溃/close 不彻底时仍杜绝临时 profile
|
|
241
|
+
// 孤儿 Chrome 堆积(实测同时存活 ≥2 棵孤儿 Chrome 树的根因)。
|
|
242
|
+
await reclaimStaleDaemons(reclaim);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export async function getStreamPort(
|
|
246
|
+
sessionName = 'minimal-agent',
|
|
247
|
+
deps: DaemonDeps = realDaemonDeps,
|
|
248
|
+
): Promise<number | null> {
|
|
249
|
+
const cached = ports.get(sessionName);
|
|
250
|
+
if (cached) return cached;
|
|
251
|
+
const bin = await deps.resolveBin();
|
|
252
|
+
if (!bin) return null;
|
|
253
|
+
const port = (await readStatus(bin, sessionName, {}, deps)).port;
|
|
254
|
+
if (port) ports.set(sessionName, port);
|
|
255
|
+
return port;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export async function waitForStreamPort(
|
|
259
|
+
sessionName = 'minimal-agent',
|
|
260
|
+
timeoutMs = 10_000,
|
|
261
|
+
deps: DaemonDeps = realDaemonDeps,
|
|
262
|
+
): Promise<number> {
|
|
263
|
+
const deadline = Date.now() + timeoutMs;
|
|
264
|
+
while (Date.now() < deadline) {
|
|
265
|
+
const port = await getStreamPort(sessionName, deps);
|
|
266
|
+
if (port) return port;
|
|
267
|
+
await new Promise((resolve) => setTimeout(resolve, 120));
|
|
268
|
+
}
|
|
269
|
+
throw new Error('浏览器已启动,但实时画面端口未就绪');
|
|
270
|
+
}
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ============================================================
|
|
3
|
+
* browserOutcome.ts —— 浏览器动作的「成功率」设施
|
|
4
|
+
* ------------------------------------------------------------
|
|
5
|
+
* 三件事,全部是纯函数,因此可以被单测钉死:
|
|
6
|
+
* ① 页面指纹与变化判定(动作到底生效了没有)
|
|
7
|
+
* ② 选择器失败时的候选推荐(把「没找到」变成「最接近的是这几个」)
|
|
8
|
+
* ③ 同一目标连续失败的熔断计数
|
|
9
|
+
*
|
|
10
|
+
* 为什么这些属于「成功率」而不是「锦上添花」:模型的每一次重试都是一个 LLM 往返,
|
|
11
|
+
* 而它重试时**手上没有当前页面**——失败只回了一句 `selector not found`。于是最常见
|
|
12
|
+
* 的失败形状不是「做错了」,是「原地换着选择器盲猜三五轮,然后放弃」。
|
|
13
|
+
* ============================================================
|
|
14
|
+
*/
|
|
15
|
+
/** 会被验证「是否真的改变了页面」的动作。 */
|
|
16
|
+
export const VERIFIED_BROWSER_ACTIONS = new Set([
|
|
17
|
+
'click',
|
|
18
|
+
'fill',
|
|
19
|
+
'type',
|
|
20
|
+
'select',
|
|
21
|
+
'press',
|
|
22
|
+
'drag',
|
|
23
|
+
]);
|
|
24
|
+
/**
|
|
25
|
+
* 两份快照中内容不同的行号。行数不同则返回 null —— 那本身就是结构变化。
|
|
26
|
+
*
|
|
27
|
+
* ⚠️ 两边都先 `trim()`。动作前的快照是原始 stdout、动作后那份在别处被 trim 过,
|
|
28
|
+
* 一个尾随换行就让行数对不上、直接判成「结构变了」,于是**所有**动作都判 changed,
|
|
29
|
+
* 噪声扣除彻底失效。基准集跑出来才发现——纯逻辑单测里两边都是我手写的字符串,
|
|
30
|
+
* 永远不会有这种不对称。
|
|
31
|
+
*/
|
|
32
|
+
export function differingLineIndexes(a, b) {
|
|
33
|
+
const left = a.trim().split('\n');
|
|
34
|
+
const right = b.trim().split('\n');
|
|
35
|
+
if (left.length !== right.length)
|
|
36
|
+
return null;
|
|
37
|
+
const diff = new Set();
|
|
38
|
+
for (let i = 0; i < left.length; i += 1) {
|
|
39
|
+
if (left[i].trim() !== right[i].trim())
|
|
40
|
+
diff.add(i);
|
|
41
|
+
}
|
|
42
|
+
return diff;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* 判定一次动作是否真的改变了页面。
|
|
46
|
+
*
|
|
47
|
+
* **噪声是测出来的,不是猜出来的**:`after` 之后隔一小段再拍一张 `noiseAfter`,
|
|
48
|
+
* 两张之间自己变了的**行位置**就是噪声源(时钟、未读数、轮询列表)。再看
|
|
49
|
+
* 「动作前→动作后」变了的行里,除掉这些位置之后还剩不剩。
|
|
50
|
+
*
|
|
51
|
+
* ⚠️ 噪声基线取在**动作之后**而不是之前。噪声是页面的持续属性,前后测等价;
|
|
52
|
+
* 而放在后面换来两件事:`before === after` 时(正是要判 unchanged 的情形)根本
|
|
53
|
+
* 不必采样,零成本;采样的那段等待也不再推迟动作本身的发出。
|
|
54
|
+
*
|
|
55
|
+
* 三个实测踩过的坑,这里同时避开:
|
|
56
|
+
* · 直接比全页文本 → 时钟每 200ms 一跳,恒判「变了」,判定形同虚设;
|
|
57
|
+
* · 忽略所有文本只看结构 → 点击把 `StaticText "closed"` 变成 `"opened"` 这种
|
|
58
|
+
* **真正生效**的纯文本变化被报成「完全没有变化」。误报比不报更糟:它在教模型
|
|
59
|
+
* 不要相信一个正确的动作;
|
|
60
|
+
* · 按行**内容**标定噪声 → 时钟每次都是新值,永远匹配不上自己。所以按行**位置**。
|
|
61
|
+
*
|
|
62
|
+
* 行数一变就直接算 changed —— 结构动了,不必再细究。
|
|
63
|
+
*/
|
|
64
|
+
export function classifyPageEffect(before, after, noiseAfter) {
|
|
65
|
+
if (before === undefined || after === undefined)
|
|
66
|
+
return { effect: 'unknown', maskedLines: 0 };
|
|
67
|
+
const actual = differingLineIndexes(before, after);
|
|
68
|
+
if (actual === null)
|
|
69
|
+
return { effect: 'changed', maskedLines: 0 }; // 行数变了 = 结构变了
|
|
70
|
+
if (actual.size === 0)
|
|
71
|
+
return { effect: 'unchanged', maskedLines: 0 };
|
|
72
|
+
const noisy = noiseAfter !== undefined ? differingLineIndexes(after, noiseAfter) : null;
|
|
73
|
+
// 没有可用的噪声基线时不做扣除:宁可把「生效了」判宽,也不能把一次真实变化
|
|
74
|
+
// 误报成没变——那会让模型放弃一条本来走通了的路。
|
|
75
|
+
if (noisy === null)
|
|
76
|
+
return { effect: 'changed', maskedLines: 0 };
|
|
77
|
+
for (const index of actual)
|
|
78
|
+
if (!noisy.has(index))
|
|
79
|
+
return { effect: 'changed', maskedLines: 0 };
|
|
80
|
+
return { effect: 'unchanged', maskedLines: actual.size };
|
|
81
|
+
}
|
|
82
|
+
/** 给模型的效果说明。措辞要让它知道**下一步该干什么**,不只是知道结果。 */
|
|
83
|
+
export function describeEffect(verdict, action) {
|
|
84
|
+
switch (verdict.effect) {
|
|
85
|
+
case 'changed':
|
|
86
|
+
return null; // 正常路径不加噪声
|
|
87
|
+
case 'unchanged':
|
|
88
|
+
return (`⚠️ 动作已发出,但页面结构在动作前后**完全没有变化**。\n`
|
|
89
|
+
+ `这通常意味着三种情况之一:${action} 命中的不是可交互元素、点击被遮挡层拦住了、`
|
|
90
|
+
+ `或者这个操作本来就不改变页面(比如点了一个纯展示文本)。\n`
|
|
91
|
+
+ `请对照下面的最新快照确认目标是否正确,不要用同一个选择器直接重试。`
|
|
92
|
+
// 豁免了行才提。这不是免责声明,是让模型知道这个结论有多硬:
|
|
93
|
+
// 被豁免的位置上,「自己在跳」和「被我改了又在跳」不可区分。
|
|
94
|
+
+ (verdict.maskedLines > 0
|
|
95
|
+
? `\n(说明:页面上有 ${verdict.maskedLines} 行在持续自行变化(时钟/轮询一类),`
|
|
96
|
+
+ `已从判定中排除。若你这次操作本该影响的正是这些位置,这个结论可能不准。)`
|
|
97
|
+
: ''));
|
|
98
|
+
case 'unknown':
|
|
99
|
+
return '(未能取得动作前后的页面快照,无法判断这次操作是否生效——请自行核对。)';
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* 解析快照里的元素引用行。
|
|
104
|
+
*
|
|
105
|
+
* 匹配 `ref=eN` 而不是 `[ref=eN]`:真实行是 `[level=1, ref=e1]`,ref 常和别的属性
|
|
106
|
+
* 混在同一个方括号里。第一版按 `[ref=` 起头写,于是带属性的行(heading/button 这类
|
|
107
|
+
* 最该被推荐的)全部漏掉——真实样本进测试之后才暴露。
|
|
108
|
+
*
|
|
109
|
+
* ⚠️ **两种形状都要认**。文档和工具描述里写的是 `@eN`,但 agent-browser 实际输出的是
|
|
110
|
+
* `[ref=e1]`:
|
|
111
|
+
* `- link "Learn more" [ref=e2]`
|
|
112
|
+
* 第一版只认 `@eN`,于是候选推荐在真实页面上**一个都匹配不到** —— 单测全绿,实机零命中。
|
|
113
|
+
* 是跑了一次真的 example.com 才发现的。两种都收,并统一成 `@eN` 交给模型(那是它被教的
|
|
114
|
+
* 引用写法)。
|
|
115
|
+
*/
|
|
116
|
+
export function parseSnapshotCandidates(snapshot) {
|
|
117
|
+
const out = [];
|
|
118
|
+
for (const raw of snapshot.split('\n')) {
|
|
119
|
+
const line = raw.trim();
|
|
120
|
+
const match = /@e(\d+)|\bref=e(\d+)/.exec(line);
|
|
121
|
+
if (!match)
|
|
122
|
+
continue;
|
|
123
|
+
const id = match[1] ?? match[2];
|
|
124
|
+
out.push({ ref: `@e${id}`, line: line.slice(0, 160) });
|
|
125
|
+
}
|
|
126
|
+
return out;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* 归一化成可比较的词:小写、去标点、拆词。
|
|
130
|
+
*
|
|
131
|
+
* ⚠️ 连字符和下划线**必须**当分隔符。真实 CSS 选择器几乎全是 `a.learn-more-link`、
|
|
132
|
+
* `#btn_submit`、`[data-testid=...]` 这个形状,不拆的话整串是一个词,和快照里的
|
|
133
|
+
* `link "Learn more"` 永远匹配不上 —— 功能在最常见的输入上直接失效。
|
|
134
|
+
* 第一版漏了这两个字符,单测(用的是中文与空格分词的样本)全绿,实机零命中。
|
|
135
|
+
*/
|
|
136
|
+
function tokenize(value) {
|
|
137
|
+
return value
|
|
138
|
+
.toLowerCase()
|
|
139
|
+
.replace(/[#.>[\]="'`(),:{}\-_/*+~|]/g, ' ')
|
|
140
|
+
.split(/\s+/)
|
|
141
|
+
.filter((token) => token.length > 1);
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* 选择器没命中时,从快照里挑出最像目标的几个候选。
|
|
145
|
+
*
|
|
146
|
+
* 打分只用「词重叠」这一件事,不引模糊匹配库:这里要的不是排序精度,是**给模型一个
|
|
147
|
+
* 能立刻拿去用的 `@eN` 列表**,而不是让它对着 `selector not found` 再猜一轮。
|
|
148
|
+
* 排名差一点无所谓,列表里有正确答案就赢了。
|
|
149
|
+
*/
|
|
150
|
+
export function suggestSelectorCandidates(snapshot, selector, limit = 5) {
|
|
151
|
+
const wanted = tokenize(selector);
|
|
152
|
+
if (wanted.length === 0)
|
|
153
|
+
return [];
|
|
154
|
+
const scored = parseSnapshotCandidates(snapshot)
|
|
155
|
+
.map((candidate) => {
|
|
156
|
+
const tokens = new Set(tokenize(candidate.line));
|
|
157
|
+
let score = 0;
|
|
158
|
+
for (const token of wanted) {
|
|
159
|
+
if (tokens.has(token))
|
|
160
|
+
score += 2;
|
|
161
|
+
else if (candidate.line.toLowerCase().includes(token))
|
|
162
|
+
score += 1;
|
|
163
|
+
}
|
|
164
|
+
return { candidate, score };
|
|
165
|
+
})
|
|
166
|
+
.filter((entry) => entry.score > 0)
|
|
167
|
+
.sort((a, b) => b.score - a.score)
|
|
168
|
+
.slice(0, limit);
|
|
169
|
+
return scored.map((entry) => entry.candidate);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* 同一目标连续失败的熔断计数。
|
|
173
|
+
*
|
|
174
|
+
* 语义**刻意与 ComputerUse 的 `refuseWhenStuck` 一致**:清零的条件是「**这个动作**
|
|
175
|
+
* 成功了一次」,读取类动作(snapshot/getContent)成功**不清**写操作的账。
|
|
176
|
+
* 原因在那边写过:`失败 → snapshot → 失败 → snapshot …` 正是模型打转时最常见的形状,
|
|
177
|
+
* 让 snapshot 清零等于熔断永远数不到上限。
|
|
178
|
+
*/
|
|
179
|
+
export class BrowserAttemptLedger {
|
|
180
|
+
counts = new Map();
|
|
181
|
+
static key(conversationKey, action, selector) {
|
|
182
|
+
return `${conversationKey}${action}${selector ?? ''}`;
|
|
183
|
+
}
|
|
184
|
+
recordFailure(conversationKey, action, selector) {
|
|
185
|
+
const key = BrowserAttemptLedger.key(conversationKey, action, selector);
|
|
186
|
+
const next = (this.counts.get(key) ?? 0) + 1;
|
|
187
|
+
this.counts.set(key, next);
|
|
188
|
+
return next;
|
|
189
|
+
}
|
|
190
|
+
recordSuccess(conversationKey, action, selector) {
|
|
191
|
+
this.counts.delete(BrowserAttemptLedger.key(conversationKey, action, selector));
|
|
192
|
+
}
|
|
193
|
+
failures(conversationKey, action, selector) {
|
|
194
|
+
return this.counts.get(BrowserAttemptLedger.key(conversationKey, action, selector)) ?? 0;
|
|
195
|
+
}
|
|
196
|
+
/** 会话结束时清账,避免长期驻留。 */
|
|
197
|
+
forget(conversationKey) {
|
|
198
|
+
for (const key of [...this.counts.keys()]) {
|
|
199
|
+
if (key.startsWith(`${conversationKey}`))
|
|
200
|
+
this.counts.delete(key);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
/** 同一 (动作, 选择器) 连续失败到这个次数就拒绝再试。 */
|
|
205
|
+
export const BROWSER_FAILURE_LIMIT = 3;
|
|
206
|
+
export const browserAttempts = new BrowserAttemptLedger();
|
|
207
|
+
/** 单行最长保留字符数;超出就地截断,不因为一行长就丢掉整行的引用信息。 */
|
|
208
|
+
const MAX_SNAPSHOT_LINE_CHARS = 300;
|
|
209
|
+
/**
|
|
210
|
+
* 把快照收进上下文预算。
|
|
211
|
+
*
|
|
212
|
+
* 失败回执里带整份快照曾经是**最贵的一笔开销**:大页面 30k 字符 ≈ 8k token,而一次
|
|
213
|
+
* 失败往往连着几次。更糟的是这些 token 大半花在纯文本节点上——模型下一步要用的是
|
|
214
|
+
* **可引用的元素**(带 ref 的行),成段的正文对「换个目标重试」毫无帮助。
|
|
215
|
+
*
|
|
216
|
+
* 所以预算不是简单截断,是**按价值取舍**:带 ref 的行优先保留(那是可操作面),
|
|
217
|
+
* 纯文本行只在还有余量时补。
|
|
218
|
+
*
|
|
219
|
+
* ⚠️ 但取舍**只决定留哪些行,不决定行的顺序**。快照是一棵缩进树,把带 ref 的行
|
|
220
|
+
* 全部提到前面会让缩进层级与父子关系错位——模型看到的是一棵结构被打乱、看上去却
|
|
221
|
+
* 仍然合法的树,判断「这个按钮在哪个对话框里」时比截断更有害。所以输出严格按原序,
|
|
222
|
+
* 连续被删的段落就地留一条 `… 省略 N 行` 占位,让模型知道**哪里**有东西被省了。
|
|
223
|
+
*/
|
|
224
|
+
export function budgetSnapshot(snapshot, opts = {}) {
|
|
225
|
+
const maxLines = opts.maxLines ?? 80;
|
|
226
|
+
const maxChars = opts.maxChars ?? 6_000;
|
|
227
|
+
const lines = snapshot.split('\n').filter((line) => line.trim().length > 0);
|
|
228
|
+
if (lines.length <= maxLines && snapshot.length <= maxChars)
|
|
229
|
+
return snapshot;
|
|
230
|
+
const hasRef = (line) => /@e\d+|\bref=e\d+/.test(line);
|
|
231
|
+
const clip = (line) => line.length <= MAX_SNAPSHOT_LINE_CHARS ? line : `${line.slice(0, MAX_SNAPSHOT_LINE_CHARS)}…`;
|
|
232
|
+
// 两轮**只挑不排**:先在原序上标记要留的可操作行,再用余量补纯文本行。
|
|
233
|
+
const keep = new Array(lines.length).fill(false);
|
|
234
|
+
let kept = 0;
|
|
235
|
+
let chars = 0;
|
|
236
|
+
const take = (index) => {
|
|
237
|
+
if (keep[index] || kept >= maxLines)
|
|
238
|
+
return;
|
|
239
|
+
const cost = clip(lines[index]).length + 1;
|
|
240
|
+
// 放不下就跳过这一行继续看下一行——`break` 会让一条超长行连累后面所有放得下的。
|
|
241
|
+
if (chars + cost > maxChars)
|
|
242
|
+
return;
|
|
243
|
+
keep[index] = true;
|
|
244
|
+
kept += 1;
|
|
245
|
+
chars += cost;
|
|
246
|
+
};
|
|
247
|
+
for (let i = 0; i < lines.length; i += 1)
|
|
248
|
+
if (hasRef(lines[i]))
|
|
249
|
+
take(i);
|
|
250
|
+
for (let i = 0; i < lines.length; i += 1)
|
|
251
|
+
if (!hasRef(lines[i]))
|
|
252
|
+
take(i);
|
|
253
|
+
const out = [];
|
|
254
|
+
let gap = 0;
|
|
255
|
+
const flushGap = () => {
|
|
256
|
+
if (gap > 0)
|
|
257
|
+
out.push(` … 省略 ${gap} 行`);
|
|
258
|
+
gap = 0;
|
|
259
|
+
};
|
|
260
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
261
|
+
if (keep[i]) {
|
|
262
|
+
flushGap();
|
|
263
|
+
out.push(clip(lines[i]));
|
|
264
|
+
}
|
|
265
|
+
else
|
|
266
|
+
gap += 1;
|
|
267
|
+
}
|
|
268
|
+
flushGap();
|
|
269
|
+
const dropped = lines.length - kept;
|
|
270
|
+
return (`${out.join('\n')}\n`
|
|
271
|
+
+ `… 为控制上下文,共省略 ${dropped} 行(按原顺序保留,优先保住可引用的元素)。`
|
|
272
|
+
+ '需要完整页面请单独调用 snapshot,或用 getContent 读取正文。');
|
|
273
|
+
}
|