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,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ============================================================
|
|
3
|
+
* vision/settle.ts —— 稳态判定(纯函数)
|
|
4
|
+
* ------------------------------------------------------------
|
|
5
|
+
* ★ 本模块存在的理由是一条**被推翻的假设**,写在这里防止后人改回去。
|
|
6
|
+
*
|
|
7
|
+
* 最初的设计是 `settle = ROI 稳态 AND 焦点未变`,把像素与焦点当**等权**信号。
|
|
8
|
+
* 支撑它的论证是:「输入法必须知道 caret 位置才能画候选词框,所以哪怕自绘应用
|
|
9
|
+
* 也堵不住这个泄漏点,焦点 L2 总能拿到」。
|
|
10
|
+
*
|
|
11
|
+
* 这个论证是**错的**。IME 通道(Windows TSF/IMM、macOS TSM)与无障碍通道
|
|
12
|
+
* (GetGUIThreadInfo、AX API)是两条**独立**路径,前者不向第三方进程暴露:
|
|
13
|
+
* - Windows:`GetGUIThreadInfo.hwndCaret` 对 Chromium/Electron 恒为 NULL
|
|
14
|
+
* (自绘控件没有真实 HWND)—— Mozilla bug 275171
|
|
15
|
+
* - macOS:`AXSelectedTextRange` 在 Mac App Store 版 Electron 被整体 patch 掉,
|
|
16
|
+
* all-or-nothing,客户端再怎么查都没有 —— electron#22908
|
|
17
|
+
* 而 Electron / 自绘控件**正是本能力的目标场景**(微信、企业自研客户端)。
|
|
18
|
+
*
|
|
19
|
+
* 本仓库已实测确认这一点:Claude 桌面端(Electron)的 AX 树只暴露窗口本身与
|
|
20
|
+
* 三个窗口按钮,应用内容一个节点都没有。
|
|
21
|
+
*
|
|
22
|
+
* 所以判据必须重排:
|
|
23
|
+
*
|
|
24
|
+
* settled ← **只看 ROI 像素稳态**。像素永远可用,这是唯一可靠的主判据。
|
|
25
|
+
* interrupted ← 焦点**跨进程**变化。L1 就能判(前台窗口归 OS 管),可靠,保留为硬信号。
|
|
26
|
+
* enhanced ← 焦点**同窗口内**变化(控件角色/caret 变)。只有 L2 可用时才有,
|
|
27
|
+
* 拿不到就**不报**,绝不因为「没检测到」就断言「没发生」。
|
|
28
|
+
* confidence ← 如实告诉调用方这次判定有多硬。
|
|
29
|
+
*
|
|
30
|
+
* 关键取舍:**焦点不再能否决主判据。**
|
|
31
|
+
* 同窗口内弹层(微信搜索联想那类)在 L2 不可用时靠 ROI 像素变化检出 ——
|
|
32
|
+
* 弹层出现必然改变像素,所以 ROI 不稳定,settle 自然为假。焦点只是让我们
|
|
33
|
+
* **更早、更明确**地知道原因,而不是唯一的发现途径。
|
|
34
|
+
* ============================================================
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
import { type FrameDiff, diffFrames, type TimelinePoint } from './frames.js';
|
|
38
|
+
import {
|
|
39
|
+
FOCUS_CHANGE,
|
|
40
|
+
type FocusExpectation,
|
|
41
|
+
type FocusSnapshot,
|
|
42
|
+
classifyFocusChange,
|
|
43
|
+
} from './focus.js';
|
|
44
|
+
import { stableMillis } from './frames.js';
|
|
45
|
+
import type { Rect } from './raster.js';
|
|
46
|
+
|
|
47
|
+
/** 判定强度。模型据此决定要不要再确认一次。 */
|
|
48
|
+
export const CONFIDENCE = {
|
|
49
|
+
/** 没有连续帧时间线,无法判定稳定。 */
|
|
50
|
+
UNKNOWN: 'unknown',
|
|
51
|
+
/** 焦点 L2 可用:像素 + 焦点双通道 */
|
|
52
|
+
HIGH: 'high',
|
|
53
|
+
/** 只有像素通道(焦点退到 L1,看不见同窗口内的焦点迁移) */
|
|
54
|
+
PIXEL_ONLY: 'pixel-only',
|
|
55
|
+
} as const;
|
|
56
|
+
|
|
57
|
+
export type Confidence = (typeof CONFIDENCE)[keyof typeof CONFIDENCE];
|
|
58
|
+
|
|
59
|
+
/** 默认静默窗口。点击 200ms 足够,页面跳转要 800ms —— 由调用方按动作类型指定。 */
|
|
60
|
+
export const DEFAULT_QUIET_MS = 300;
|
|
61
|
+
|
|
62
|
+
/** 低于此变化占比算「安静」。0.001 在 480×270 上约等于 130 像素。 */
|
|
63
|
+
export const DEFAULT_QUIET_RATIO = 0.001;
|
|
64
|
+
|
|
65
|
+
export interface SettleVerdict {
|
|
66
|
+
/** ROI 是否已稳定(**主判据**) */
|
|
67
|
+
settled: boolean;
|
|
68
|
+
/** ROI 已静止多久 */
|
|
69
|
+
stableMs: number;
|
|
70
|
+
/** 是否被跨进程打断(硬信号,L1 即可判) */
|
|
71
|
+
interrupted: boolean;
|
|
72
|
+
/** 同窗口内焦点变化(仅 L2 可用时非 null) */
|
|
73
|
+
enhanced: string | null;
|
|
74
|
+
confidence: Confidence;
|
|
75
|
+
/** 人话说明,直接给模型看 */
|
|
76
|
+
reason: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface EvaluateSettleArgs {
|
|
80
|
+
/** 时间线(**应已按 ROI 限定**)。原生 settle 返回的就是这个形状。 */
|
|
81
|
+
timeline: TimelinePoint[];
|
|
82
|
+
now: number;
|
|
83
|
+
focusBefore?: FocusSnapshot | null;
|
|
84
|
+
focusAfter?: FocusSnapshot | null;
|
|
85
|
+
expectation?: FocusExpectation;
|
|
86
|
+
quietMs?: number;
|
|
87
|
+
quietRatio?: number;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function evaluateSettle(args: EvaluateSettleArgs): SettleVerdict {
|
|
91
|
+
const {
|
|
92
|
+
timeline,
|
|
93
|
+
now,
|
|
94
|
+
focusBefore = null,
|
|
95
|
+
focusAfter = null,
|
|
96
|
+
expectation = {},
|
|
97
|
+
quietMs = DEFAULT_QUIET_MS,
|
|
98
|
+
quietRatio = DEFAULT_QUIET_RATIO,
|
|
99
|
+
} = args;
|
|
100
|
+
|
|
101
|
+
// 单样本没有时间跨度,不能通过把 `now` 人为后移来伪造稳定时长。
|
|
102
|
+
if (!Array.isArray(timeline) || timeline.length < 2) {
|
|
103
|
+
return {
|
|
104
|
+
settled: false,
|
|
105
|
+
stableMs: 0,
|
|
106
|
+
interrupted: false,
|
|
107
|
+
enhanced: null,
|
|
108
|
+
confidence: CONFIDENCE.UNKNOWN,
|
|
109
|
+
reason: '稳态未知:缺少连续帧时间线',
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ---- 主判据:像素。永远可用,不依赖任何平台能力。
|
|
114
|
+
const stable = stableMillis(timeline, now, quietRatio);
|
|
115
|
+
const settled = stable >= quietMs;
|
|
116
|
+
|
|
117
|
+
// ---- 焦点通道:能拿到哪层就用哪层,拿不到就不报(不假装)
|
|
118
|
+
const level = focusAfter?.level ?? focusBefore?.level ?? 0;
|
|
119
|
+
const change = classifyFocusChange(focusBefore, focusAfter, expectation);
|
|
120
|
+
|
|
121
|
+
// 跨进程打断只需 L1(前台窗口/进程归 OS 管,任何应用都藏不住),故恒可信
|
|
122
|
+
const interrupted = change === FOCUS_CHANGE.OTHER_PROCESS;
|
|
123
|
+
|
|
124
|
+
// 同窗口内的焦点迁移需要 L2(控件角色 / caret)。L1 下**永远检测不到**,
|
|
125
|
+
// 此时必须报 null 而不是 'none' —— 后者会被误读成「确认没发生」。
|
|
126
|
+
const enhanced =
|
|
127
|
+
level >= 2 && change === FOCUS_CHANGE.SAME_WINDOW ? FOCUS_CHANGE.SAME_WINDOW : null;
|
|
128
|
+
|
|
129
|
+
const confidence = level >= 2 ? CONFIDENCE.HIGH : CONFIDENCE.PIXEL_ONLY;
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
settled,
|
|
133
|
+
stableMs: stable,
|
|
134
|
+
interrupted,
|
|
135
|
+
enhanced,
|
|
136
|
+
confidence,
|
|
137
|
+
reason: describe({ settled, stable, quietMs, interrupted, enhanced, confidence }),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function describe(input: {
|
|
142
|
+
settled: boolean;
|
|
143
|
+
stable: number;
|
|
144
|
+
quietMs: number;
|
|
145
|
+
interrupted: boolean;
|
|
146
|
+
enhanced: string | null;
|
|
147
|
+
confidence: Confidence;
|
|
148
|
+
}): string {
|
|
149
|
+
const parts: string[] = [];
|
|
150
|
+
parts.push(
|
|
151
|
+
input.settled
|
|
152
|
+
? `界面已稳定(静止 ${input.stable}ms)`
|
|
153
|
+
: `界面仍在变化(静止仅 ${input.stable}ms,需 ${input.quietMs}ms)`,
|
|
154
|
+
);
|
|
155
|
+
if (input.interrupted) parts.push('⚠️ 焦点被**其它进程**抢走,属外部打断');
|
|
156
|
+
if (input.enhanced) parts.push('⚠️ 同窗口内焦点发生迁移(应用弹出了新界面元素)');
|
|
157
|
+
if (input.confidence === CONFIDENCE.PIXEL_ONLY) {
|
|
158
|
+
// 这句必须说:不说的话模型会把「没报焦点变化」当成「焦点没变」
|
|
159
|
+
parts.push('(仅像素判据:当前后端拿不到控件级焦点,同窗口内的焦点迁移无法检出)');
|
|
160
|
+
}
|
|
161
|
+
return parts.join(';');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export interface AbortTypingArgs {
|
|
165
|
+
focusBefore?: FocusSnapshot | null;
|
|
166
|
+
focusAfter?: FocusSnapshot | null;
|
|
167
|
+
expectation?: FocusExpectation;
|
|
168
|
+
roiDiff?: FrameDiff | null;
|
|
169
|
+
changeRatioLimit?: number;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* 输入过程中的逐步判定 —— 「打字打到一半界面变了」的核心。
|
|
174
|
+
*
|
|
175
|
+
* 同样遵循「焦点不否决主判据」:
|
|
176
|
+
* - 跨进程打断 → 必停(L1 可判,可靠)
|
|
177
|
+
* - 同窗口迁移 → L2 可用时必停;L2 不可用时**退回像素**:目标区域出现显著
|
|
178
|
+
* 变化就停(弹层出现必然改变像素)
|
|
179
|
+
*/
|
|
180
|
+
export function shouldAbortTyping(args: AbortTypingArgs): { abort: boolean; reason: string | null } {
|
|
181
|
+
const {
|
|
182
|
+
focusBefore,
|
|
183
|
+
focusAfter,
|
|
184
|
+
expectation = {},
|
|
185
|
+
roiDiff = null,
|
|
186
|
+
changeRatioLimit = 0.02,
|
|
187
|
+
} = args;
|
|
188
|
+
|
|
189
|
+
const change = classifyFocusChange(focusBefore, focusAfter, expectation);
|
|
190
|
+
if (change === FOCUS_CHANGE.OTHER_PROCESS) {
|
|
191
|
+
return { abort: true, reason: '焦点被其它进程抢走' };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const level = focusAfter?.level ?? 0;
|
|
195
|
+
if (level >= 2 && change === FOCUS_CHANGE.SAME_WINDOW) {
|
|
196
|
+
return { abort: true, reason: '应用在同窗口内弹出了新界面元素并转移了焦点' };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// L2 拿不到时的像素兜底:输入过程中目标区域不该发生大面积变化
|
|
200
|
+
if (level < 2 && roiDiff && roiDiff.ratio > changeRatioLimit) {
|
|
201
|
+
return {
|
|
202
|
+
abort: true,
|
|
203
|
+
reason: `输入区域出现显著变化(${(roiDiff.ratio * 100).toFixed(1)}%),疑似弹出了联想/补全面板`,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return { abort: false, reason: null };
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** 便利封装:直接用两帧算 ROI 变化,供 shouldAbortTyping 的像素兜底使用。 */
|
|
211
|
+
export function roiChange(
|
|
212
|
+
before: Uint8Array,
|
|
213
|
+
after: Uint8Array,
|
|
214
|
+
width: number,
|
|
215
|
+
height: number,
|
|
216
|
+
roi?: Rect,
|
|
217
|
+
threshold?: number,
|
|
218
|
+
): FrameDiff {
|
|
219
|
+
return diffFrames(before, after, width, height, { roi, threshold });
|
|
220
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ============================================================
|
|
3
|
+
* vision/telemetry.ts —— 调用遥测与汇总
|
|
4
|
+
* ------------------------------------------------------------
|
|
5
|
+
* 存在的理由:整套设计里有两个**未经验证的假设**支撑着核心决策 ——
|
|
6
|
+
*
|
|
7
|
+
* ① 「大多数动作不需要图」(成本模型建立其上,但从没实测过,
|
|
8
|
+
* 是从「人怎么操作电脑」类比来的。而 agent 没有连续感知、没有肌肉记忆,
|
|
9
|
+
* 很可能远比人更依赖看图)
|
|
10
|
+
* ② 「焦点 L2 通常可用」(已查证在 Electron/自绘应用上大概率**不可用**,
|
|
11
|
+
* 本仓库实测也确认了这点;但到底多大比例,只有持续实测能答)
|
|
12
|
+
*
|
|
13
|
+
* 没有数据就只能靠推理,而推理已经错过一次(见 settle.ts 头注)。
|
|
14
|
+
* 本模块把每次调用的**元数据**记下来,汇总成可判定的数字。
|
|
15
|
+
*
|
|
16
|
+
* ★ 隐私硬约束(不可放宽):
|
|
17
|
+
* 只记命令名、耗时、枚举值、比例。
|
|
18
|
+
* **绝不记录**:屏幕内容、截图路径、输入的文本、窗口标题、应用内数据。
|
|
19
|
+
* 持续截屏本身已经是隐私敏感的,日志再泄漏内容就无法向客户交代。
|
|
20
|
+
* 实现是**白名单**而不是调用方自觉,守护测试见 test/computerUseVision.test.ts。
|
|
21
|
+
* ============================================================
|
|
22
|
+
*/
|
|
23
|
+
import { appendFile, mkdir, readFile, rename, rm, stat } from 'node:fs/promises';
|
|
24
|
+
import { dirname } from 'node:path';
|
|
25
|
+
/** 连续失败/无效动作达到此数,引擎**强制**拒绝同类动作。 */
|
|
26
|
+
export const CONSECUTIVE_FAILURE_LIMIT = 3;
|
|
27
|
+
/** 只允许出现在遥测行里的字段。多一个都不行 —— 白名单而非黑名单。 */
|
|
28
|
+
const ALLOWED_FIELDS = new Set([
|
|
29
|
+
't', 'cmd', 'ms', 'backend', 'degraded', 'focusLevel', 'images', 'estTokens',
|
|
30
|
+
'changeRatio', 'settled', 'stableMs', 'settleConfidence', 'focusChange',
|
|
31
|
+
'settleMode', 'interrupted', 'refused', 'ok',
|
|
32
|
+
'frames', 'axNodes', 'axTruncated',
|
|
33
|
+
// 不可逆动作审批的结局(短枚举,如 irreversible_approved / irreversible_denied)。
|
|
34
|
+
//
|
|
35
|
+
// 刻意**不**复用 `refused`:那个字段参与连续失败计数,而「用户不批准这次
|
|
36
|
+
// 提交」不该把后续无关的写操作一起熔断掉。审批结局要能被审计到,但不能
|
|
37
|
+
// 污染「模型是不是在原地打转」这个判断。
|
|
38
|
+
'gate',
|
|
39
|
+
]);
|
|
40
|
+
/** 字符串字段的长度上限:只允许短枚举值,杜绝有人把标题/文本塞进来。 */
|
|
41
|
+
const MAX_STRING_CHARS = 64;
|
|
42
|
+
/**
|
|
43
|
+
* 过滤成只含白名单字段的记录。**这是隐私防线的实现点** ——
|
|
44
|
+
* 调用方多传了什么都会被丢掉,而不是靠调用方自觉。
|
|
45
|
+
*/
|
|
46
|
+
export function sanitizeRecord(raw) {
|
|
47
|
+
const out = {};
|
|
48
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
49
|
+
if (!ALLOWED_FIELDS.has(key))
|
|
50
|
+
continue;
|
|
51
|
+
if (value === undefined || value === null)
|
|
52
|
+
continue;
|
|
53
|
+
if (typeof value === 'string') {
|
|
54
|
+
out[key] = value.length > MAX_STRING_CHARS ? value.slice(0, MAX_STRING_CHARS) : value;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (typeof value === 'number' || typeof value === 'boolean')
|
|
58
|
+
out[key] = value;
|
|
59
|
+
}
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* 单个日志文件的大小上限;超过就轮转成 `.1`(只留一代)。
|
|
64
|
+
*
|
|
65
|
+
* 有上限是必需的,不是优化:一台长期跑桌面自动化的机器会攒下越来越大的
|
|
66
|
+
* 隐私数据堆。遥测的用途是看比例和趋势,最近这些条完全够用。
|
|
67
|
+
*/
|
|
68
|
+
export const MAX_LOG_BYTES = 2 * 1024 * 1024;
|
|
69
|
+
/**
|
|
70
|
+
* 追加一条记录。
|
|
71
|
+
*
|
|
72
|
+
* ★ **append-only,不是读改写。** 这里曾经是「整文件读进来 → 截断到 2000 行
|
|
73
|
+
* → 原子 rename 写回」,有两个真问题:
|
|
74
|
+
* ① 并发写互相覆盖 —— 两个进程同时读到同一份旧内容,各自加一行写回,
|
|
75
|
+
* 先写的那条就没了;临时文件名只有毫秒时间戳 + PID,同进程内并发
|
|
76
|
+
* (本模块本来就是 fire-and-forget 调用)还可能撞名
|
|
77
|
+
* ② 每条记录的成本随文件大小线性增长
|
|
78
|
+
* `appendFile` 对单行小写入在 POSIX 上是原子的,正好是日志需要的语义。
|
|
79
|
+
*
|
|
80
|
+
* **永不抛异常**:遥测是观测手段不是功能,写不进去宁可少一条。
|
|
81
|
+
*/
|
|
82
|
+
export async function record(logPath, raw) {
|
|
83
|
+
const line = `${JSON.stringify({ t: Date.now(), ...sanitizeRecord(raw) })}\n`;
|
|
84
|
+
await mkdir(dirname(logPath), { recursive: true }).catch(() => { });
|
|
85
|
+
await rotateIfLarge(logPath);
|
|
86
|
+
await appendFile(logPath, line, { mode: 0o600 }).catch(() => { });
|
|
87
|
+
}
|
|
88
|
+
/** 超限就把当前文件挪成 `.1`(覆盖上一代)。失败静默 —— 轮转不了也要能记。 */
|
|
89
|
+
async function rotateIfLarge(logPath) {
|
|
90
|
+
try {
|
|
91
|
+
const info = await stat(logPath);
|
|
92
|
+
if (info.size < MAX_LOG_BYTES)
|
|
93
|
+
return;
|
|
94
|
+
await rename(logPath, `${logPath}.1`);
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
/* 文件不存在或无法轮转,都不影响追加 */
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
export async function readRecords(logPath) {
|
|
101
|
+
const raw = await readFile(logPath, 'utf8').catch(() => '');
|
|
102
|
+
const out = [];
|
|
103
|
+
for (const line of raw.split('\n')) {
|
|
104
|
+
if (!line.trim())
|
|
105
|
+
continue;
|
|
106
|
+
try {
|
|
107
|
+
out.push(JSON.parse(line));
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
// 半行/损坏行跳过,不因日志问题打断主流程
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return out;
|
|
114
|
+
}
|
|
115
|
+
export async function clearRecords(logPath) {
|
|
116
|
+
await rm(logPath, { force: true }).catch(() => { });
|
|
117
|
+
}
|
|
118
|
+
/** 汇总。产出的每一项都直接对应一个待验证的设计假设。 */
|
|
119
|
+
export function summarize(records) {
|
|
120
|
+
const actions = records.filter((r) => ACTION_CMDS.has(String(r.cmd)));
|
|
121
|
+
const observes = records.filter((r) => r.cmd === 'observe');
|
|
122
|
+
const withImages = records.filter((r) => Number(r.images ?? 0) > 0);
|
|
123
|
+
const total = records.length;
|
|
124
|
+
const focusLevels = tally(records.map((r) => r.focusLevel).filter((v) => v !== undefined));
|
|
125
|
+
const confidences = tally(records.map((r) => r.settleConfidence).filter(Boolean));
|
|
126
|
+
const refusals = tally(records.map((r) => r.refused).filter(Boolean));
|
|
127
|
+
return {
|
|
128
|
+
total,
|
|
129
|
+
actions: actions.length,
|
|
130
|
+
observes: observes.length,
|
|
131
|
+
// ── 假设 ①:大多数动作不需要图
|
|
132
|
+
imageCallRatio: total ? withImages.length / total : 0,
|
|
133
|
+
estTokensTotal: records.reduce((sum, r) => sum + Number(r.estTokens ?? 0), 0),
|
|
134
|
+
// ── 假设 ②:焦点 L2 通常可用
|
|
135
|
+
focusLevels,
|
|
136
|
+
l2Ratio: ratioOf(focusLevels, '2'),
|
|
137
|
+
settleConfidence: confidences,
|
|
138
|
+
refusals,
|
|
139
|
+
failureRate: total ? records.filter((r) => r.ok === false).length / total : 0,
|
|
140
|
+
latency: latencyStats(records.map((r) => r.ms).filter((v) => typeof v === 'number')),
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
const ACTION_CMDS = new Set(['click', 'type', 'key', 'hotkey', 'scroll', 'drag', 'hover']);
|
|
144
|
+
function tally(values) {
|
|
145
|
+
const out = {};
|
|
146
|
+
for (const v of values) {
|
|
147
|
+
if (v === undefined)
|
|
148
|
+
continue;
|
|
149
|
+
const key = String(v);
|
|
150
|
+
out[key] = (out[key] ?? 0) + 1;
|
|
151
|
+
}
|
|
152
|
+
return out;
|
|
153
|
+
}
|
|
154
|
+
function ratioOf(tallied, key) {
|
|
155
|
+
const total = Object.values(tallied).reduce((a, b) => a + b, 0);
|
|
156
|
+
return total ? (tallied[key] ?? 0) / total : 0;
|
|
157
|
+
}
|
|
158
|
+
function latencyStats(values) {
|
|
159
|
+
if (values.length === 0)
|
|
160
|
+
return { count: 0 };
|
|
161
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
162
|
+
return {
|
|
163
|
+
count: sorted.length,
|
|
164
|
+
min: sorted[0],
|
|
165
|
+
p50: sorted[Math.floor(sorted.length * 0.5)],
|
|
166
|
+
p95: sorted[Math.floor(sorted.length * 0.95)],
|
|
167
|
+
max: sorted[sorted.length - 1],
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
/** 人话渲染。每项都标注它在验证什么假设。 */
|
|
171
|
+
export function renderReport(summary) {
|
|
172
|
+
if (summary.total === 0)
|
|
173
|
+
return '尚无遥测记录。先跑几次 ComputerUse 再看。';
|
|
174
|
+
const pct = (v) => `${(v * 100).toFixed(1)}%`;
|
|
175
|
+
const lines = [
|
|
176
|
+
`# Computer Use 遥测汇总(${summary.total} 次调用)`,
|
|
177
|
+
'',
|
|
178
|
+
`动作类 ${summary.actions} 次 · 观测类 ${summary.observes} 次 · 失败率 ${pct(summary.failureRate)}`,
|
|
179
|
+
'',
|
|
180
|
+
'## 假设 ①:大多数动作不需要图',
|
|
181
|
+
`实测**带图调用占比 ${pct(summary.imageCallRatio)}**`,
|
|
182
|
+
`累计图像成本估算 ${summary.estTokensTotal} tokens`,
|
|
183
|
+
'',
|
|
184
|
+
'## 假设 ②:焦点 L2 通常可用',
|
|
185
|
+
`实测**L2 可用率 ${pct(summary.l2Ratio)}**;各层分布 ${JSON.stringify(summary.focusLevels)}`,
|
|
186
|
+
`settle 判据强度分布 ${JSON.stringify(summary.settleConfidence)}`,
|
|
187
|
+
];
|
|
188
|
+
if (Object.keys(summary.refusals).length > 0) {
|
|
189
|
+
lines.push('', '## 引擎拒绝', JSON.stringify(summary.refusals));
|
|
190
|
+
}
|
|
191
|
+
if (summary.latency.count > 0) {
|
|
192
|
+
const l = summary.latency;
|
|
193
|
+
lines.push('', '## 耗时(ms)', `min ${l.min} · p50 ${l.p50} · p95 ${l.p95} · max ${l.max}`);
|
|
194
|
+
}
|
|
195
|
+
return lines.join('\n');
|
|
196
|
+
}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ============================================================
|
|
3
|
+
* vision/telemetry.ts —— 调用遥测与汇总
|
|
4
|
+
* ------------------------------------------------------------
|
|
5
|
+
* 存在的理由:整套设计里有两个**未经验证的假设**支撑着核心决策 ——
|
|
6
|
+
*
|
|
7
|
+
* ① 「大多数动作不需要图」(成本模型建立其上,但从没实测过,
|
|
8
|
+
* 是从「人怎么操作电脑」类比来的。而 agent 没有连续感知、没有肌肉记忆,
|
|
9
|
+
* 很可能远比人更依赖看图)
|
|
10
|
+
* ② 「焦点 L2 通常可用」(已查证在 Electron/自绘应用上大概率**不可用**,
|
|
11
|
+
* 本仓库实测也确认了这点;但到底多大比例,只有持续实测能答)
|
|
12
|
+
*
|
|
13
|
+
* 没有数据就只能靠推理,而推理已经错过一次(见 settle.ts 头注)。
|
|
14
|
+
* 本模块把每次调用的**元数据**记下来,汇总成可判定的数字。
|
|
15
|
+
*
|
|
16
|
+
* ★ 隐私硬约束(不可放宽):
|
|
17
|
+
* 只记命令名、耗时、枚举值、比例。
|
|
18
|
+
* **绝不记录**:屏幕内容、截图路径、输入的文本、窗口标题、应用内数据。
|
|
19
|
+
* 持续截屏本身已经是隐私敏感的,日志再泄漏内容就无法向客户交代。
|
|
20
|
+
* 实现是**白名单**而不是调用方自觉,守护测试见 test/computerUseVision.test.ts。
|
|
21
|
+
* ============================================================
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { appendFile, mkdir, readFile, rename, rm, stat } from 'node:fs/promises';
|
|
25
|
+
import { dirname } from 'node:path';
|
|
26
|
+
|
|
27
|
+
/** 连续失败/无效动作达到此数,引擎**强制**拒绝同类动作。 */
|
|
28
|
+
export const CONSECUTIVE_FAILURE_LIMIT = 3;
|
|
29
|
+
|
|
30
|
+
/** 只允许出现在遥测行里的字段。多一个都不行 —— 白名单而非黑名单。 */
|
|
31
|
+
const ALLOWED_FIELDS = new Set([
|
|
32
|
+
't', 'cmd', 'ms', 'backend', 'degraded', 'focusLevel', 'images', 'estTokens',
|
|
33
|
+
'changeRatio', 'settled', 'stableMs', 'settleConfidence', 'focusChange',
|
|
34
|
+
'settleMode', 'interrupted', 'refused', 'ok',
|
|
35
|
+
'frames', 'axNodes', 'axTruncated',
|
|
36
|
+
// 不可逆动作审批的结局(短枚举,如 irreversible_approved / irreversible_denied)。
|
|
37
|
+
//
|
|
38
|
+
// 刻意**不**复用 `refused`:那个字段参与连续失败计数,而「用户不批准这次
|
|
39
|
+
// 提交」不该把后续无关的写操作一起熔断掉。审批结局要能被审计到,但不能
|
|
40
|
+
// 污染「模型是不是在原地打转」这个判断。
|
|
41
|
+
'gate',
|
|
42
|
+
]);
|
|
43
|
+
|
|
44
|
+
/** 字符串字段的长度上限:只允许短枚举值,杜绝有人把标题/文本塞进来。 */
|
|
45
|
+
const MAX_STRING_CHARS = 64;
|
|
46
|
+
|
|
47
|
+
export type TelemetryRecord = Record<string, string | number | boolean>;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* 过滤成只含白名单字段的记录。**这是隐私防线的实现点** ——
|
|
51
|
+
* 调用方多传了什么都会被丢掉,而不是靠调用方自觉。
|
|
52
|
+
*/
|
|
53
|
+
export function sanitizeRecord(raw: Record<string, unknown>): TelemetryRecord {
|
|
54
|
+
const out: TelemetryRecord = {};
|
|
55
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
56
|
+
if (!ALLOWED_FIELDS.has(key)) continue;
|
|
57
|
+
if (value === undefined || value === null) continue;
|
|
58
|
+
if (typeof value === 'string') {
|
|
59
|
+
out[key] = value.length > MAX_STRING_CHARS ? value.slice(0, MAX_STRING_CHARS) : value;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (typeof value === 'number' || typeof value === 'boolean') out[key] = value;
|
|
63
|
+
}
|
|
64
|
+
return out;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 单个日志文件的大小上限;超过就轮转成 `.1`(只留一代)。
|
|
69
|
+
*
|
|
70
|
+
* 有上限是必需的,不是优化:一台长期跑桌面自动化的机器会攒下越来越大的
|
|
71
|
+
* 隐私数据堆。遥测的用途是看比例和趋势,最近这些条完全够用。
|
|
72
|
+
*/
|
|
73
|
+
export const MAX_LOG_BYTES = 2 * 1024 * 1024;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* 追加一条记录。
|
|
77
|
+
*
|
|
78
|
+
* ★ **append-only,不是读改写。** 这里曾经是「整文件读进来 → 截断到 2000 行
|
|
79
|
+
* → 原子 rename 写回」,有两个真问题:
|
|
80
|
+
* ① 并发写互相覆盖 —— 两个进程同时读到同一份旧内容,各自加一行写回,
|
|
81
|
+
* 先写的那条就没了;临时文件名只有毫秒时间戳 + PID,同进程内并发
|
|
82
|
+
* (本模块本来就是 fire-and-forget 调用)还可能撞名
|
|
83
|
+
* ② 每条记录的成本随文件大小线性增长
|
|
84
|
+
* `appendFile` 对单行小写入在 POSIX 上是原子的,正好是日志需要的语义。
|
|
85
|
+
*
|
|
86
|
+
* **永不抛异常**:遥测是观测手段不是功能,写不进去宁可少一条。
|
|
87
|
+
*/
|
|
88
|
+
export async function record(logPath: string, raw: Record<string, unknown>): Promise<void> {
|
|
89
|
+
const line = `${JSON.stringify({ t: Date.now(), ...sanitizeRecord(raw) })}\n`;
|
|
90
|
+
await mkdir(dirname(logPath), { recursive: true }).catch(() => {});
|
|
91
|
+
await rotateIfLarge(logPath);
|
|
92
|
+
await appendFile(logPath, line, { mode: 0o600 }).catch(() => {});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** 超限就把当前文件挪成 `.1`(覆盖上一代)。失败静默 —— 轮转不了也要能记。 */
|
|
96
|
+
async function rotateIfLarge(logPath: string): Promise<void> {
|
|
97
|
+
try {
|
|
98
|
+
const info = await stat(logPath);
|
|
99
|
+
if (info.size < MAX_LOG_BYTES) return;
|
|
100
|
+
await rename(logPath, `${logPath}.1`);
|
|
101
|
+
} catch {
|
|
102
|
+
/* 文件不存在或无法轮转,都不影响追加 */
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export async function readRecords(logPath: string): Promise<TelemetryRecord[]> {
|
|
107
|
+
const raw = await readFile(logPath, 'utf8').catch(() => '');
|
|
108
|
+
const out: TelemetryRecord[] = [];
|
|
109
|
+
for (const line of raw.split('\n')) {
|
|
110
|
+
if (!line.trim()) continue;
|
|
111
|
+
try {
|
|
112
|
+
out.push(JSON.parse(line) as TelemetryRecord);
|
|
113
|
+
} catch {
|
|
114
|
+
// 半行/损坏行跳过,不因日志问题打断主流程
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return out;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export async function clearRecords(logPath: string): Promise<void> {
|
|
121
|
+
await rm(logPath, { force: true }).catch(() => {});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// 这里曾有一个 `consecutiveFailures(records)`:从日志末尾往回数连续失败,
|
|
125
|
+
// 给熔断当判据。它已被删除,因为把安全策略建在一个**全机共享的日志文件**上
|
|
126
|
+
// 有三个绕不过去的问题:没有会话隔离(A 的失败熔断 B)、读改写有竞态、
|
|
127
|
+
// 计数不分动作(一次成功 observe 就清零,于是「失败→观察→失败」这个最常见的
|
|
128
|
+
// 打转模式可以无限绕过熔断)。
|
|
129
|
+
//
|
|
130
|
+
// 熔断现在住在 `ComputerUseService`:按 `运行 + 动作指纹` 分桶、内存态、同步
|
|
131
|
+
// 无 IO。本模块退回**纯观测**职责。别再把判定搬回来。
|
|
132
|
+
|
|
133
|
+
export interface LatencyStats {
|
|
134
|
+
count: number;
|
|
135
|
+
min?: number;
|
|
136
|
+
p50?: number;
|
|
137
|
+
p95?: number;
|
|
138
|
+
max?: number;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export interface TelemetrySummary {
|
|
142
|
+
total: number;
|
|
143
|
+
actions: number;
|
|
144
|
+
observes: number;
|
|
145
|
+
imageCallRatio: number;
|
|
146
|
+
estTokensTotal: number;
|
|
147
|
+
focusLevels: Record<string, number>;
|
|
148
|
+
l2Ratio: number;
|
|
149
|
+
settleConfidence: Record<string, number>;
|
|
150
|
+
refusals: Record<string, number>;
|
|
151
|
+
failureRate: number;
|
|
152
|
+
latency: LatencyStats;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** 汇总。产出的每一项都直接对应一个待验证的设计假设。 */
|
|
156
|
+
export function summarize(records: TelemetryRecord[]): TelemetrySummary {
|
|
157
|
+
const actions = records.filter((r) => ACTION_CMDS.has(String(r.cmd)));
|
|
158
|
+
const observes = records.filter((r) => r.cmd === 'observe');
|
|
159
|
+
const withImages = records.filter((r) => Number(r.images ?? 0) > 0);
|
|
160
|
+
const total = records.length;
|
|
161
|
+
|
|
162
|
+
const focusLevels = tally(records.map((r) => r.focusLevel).filter((v) => v !== undefined));
|
|
163
|
+
const confidences = tally(records.map((r) => r.settleConfidence).filter(Boolean));
|
|
164
|
+
const refusals = tally(records.map((r) => r.refused).filter(Boolean));
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
total,
|
|
168
|
+
actions: actions.length,
|
|
169
|
+
observes: observes.length,
|
|
170
|
+
// ── 假设 ①:大多数动作不需要图
|
|
171
|
+
imageCallRatio: total ? withImages.length / total : 0,
|
|
172
|
+
estTokensTotal: records.reduce((sum, r) => sum + Number(r.estTokens ?? 0), 0),
|
|
173
|
+
// ── 假设 ②:焦点 L2 通常可用
|
|
174
|
+
focusLevels,
|
|
175
|
+
l2Ratio: ratioOf(focusLevels, '2'),
|
|
176
|
+
settleConfidence: confidences,
|
|
177
|
+
refusals,
|
|
178
|
+
failureRate: total ? records.filter((r) => r.ok === false).length / total : 0,
|
|
179
|
+
latency: latencyStats(
|
|
180
|
+
records.map((r) => r.ms).filter((v): v is number => typeof v === 'number'),
|
|
181
|
+
),
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const ACTION_CMDS = new Set(['click', 'type', 'key', 'hotkey', 'scroll', 'drag', 'hover']);
|
|
186
|
+
|
|
187
|
+
function tally(values: (string | number | boolean | undefined)[]): Record<string, number> {
|
|
188
|
+
const out: Record<string, number> = {};
|
|
189
|
+
for (const v of values) {
|
|
190
|
+
if (v === undefined) continue;
|
|
191
|
+
const key = String(v);
|
|
192
|
+
out[key] = (out[key] ?? 0) + 1;
|
|
193
|
+
}
|
|
194
|
+
return out;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function ratioOf(tallied: Record<string, number>, key: string): number {
|
|
198
|
+
const total = Object.values(tallied).reduce((a, b) => a + b, 0);
|
|
199
|
+
return total ? (tallied[key] ?? 0) / total : 0;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function latencyStats(values: number[]): LatencyStats {
|
|
203
|
+
if (values.length === 0) return { count: 0 };
|
|
204
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
205
|
+
return {
|
|
206
|
+
count: sorted.length,
|
|
207
|
+
min: sorted[0]!,
|
|
208
|
+
p50: sorted[Math.floor(sorted.length * 0.5)]!,
|
|
209
|
+
p95: sorted[Math.floor(sorted.length * 0.95)]!,
|
|
210
|
+
max: sorted[sorted.length - 1]!,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** 人话渲染。每项都标注它在验证什么假设。 */
|
|
215
|
+
export function renderReport(summary: TelemetrySummary): string {
|
|
216
|
+
if (summary.total === 0) return '尚无遥测记录。先跑几次 ComputerUse 再看。';
|
|
217
|
+
|
|
218
|
+
const pct = (v: number): string => `${(v * 100).toFixed(1)}%`;
|
|
219
|
+
const lines = [
|
|
220
|
+
`# Computer Use 遥测汇总(${summary.total} 次调用)`,
|
|
221
|
+
'',
|
|
222
|
+
`动作类 ${summary.actions} 次 · 观测类 ${summary.observes} 次 · 失败率 ${pct(summary.failureRate)}`,
|
|
223
|
+
'',
|
|
224
|
+
'## 假设 ①:大多数动作不需要图',
|
|
225
|
+
`实测**带图调用占比 ${pct(summary.imageCallRatio)}**`,
|
|
226
|
+
`累计图像成本估算 ${summary.estTokensTotal} tokens`,
|
|
227
|
+
'',
|
|
228
|
+
'## 假设 ②:焦点 L2 通常可用',
|
|
229
|
+
`实测**L2 可用率 ${pct(summary.l2Ratio)}**;各层分布 ${JSON.stringify(summary.focusLevels)}`,
|
|
230
|
+
`settle 判据强度分布 ${JSON.stringify(summary.settleConfidence)}`,
|
|
231
|
+
];
|
|
232
|
+
|
|
233
|
+
if (Object.keys(summary.refusals).length > 0) {
|
|
234
|
+
lines.push('', '## 引擎拒绝', JSON.stringify(summary.refusals));
|
|
235
|
+
}
|
|
236
|
+
if (summary.latency.count > 0) {
|
|
237
|
+
const l = summary.latency;
|
|
238
|
+
lines.push('', '## 耗时(ms)', `min ${l.min} · p50 ${l.p50} · p95 ${l.p95} · max ${l.max}`);
|
|
239
|
+
}
|
|
240
|
+
return lines.join('\n');
|
|
241
|
+
}
|