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
package/src/loop.js
CHANGED
|
@@ -31,26 +31,84 @@ import { autoCompactIfNeeded, getCompactThreshold } from './context/compact.js';
|
|
|
31
31
|
import { microCompact, incrementTurn, expireOldEntries } from './context/microCompactLite.js';
|
|
32
32
|
import { isPromptTooLongError, reactiveCompactIfApplicable, } from './context/reactiveCompact.js';
|
|
33
33
|
import { snipCompactIfNeeded } from './context/snipCompact.js';
|
|
34
|
-
import { countMessagesTokens, estimateToolsTokens } from './context/tokenCounter.js';
|
|
34
|
+
import { countMessagesTokens, countTextTokens, estimateToolsTokens, } from './context/tokenCounter.js';
|
|
35
|
+
import { buildContextBudgetPlan, fitToolsForEmergency } from './context/budgetPlan.js';
|
|
36
|
+
import { resizeContextBundle } from './context/contextBundle.js';
|
|
35
37
|
import { chat as defaultChat } from './llm/client.js';
|
|
36
38
|
import { buildUserContent } from './multimodal.js';
|
|
37
|
-
import { ALL_TOOLS, executeTool as defaultExecuteTool, getToolByName } from './tools/index.js';
|
|
39
|
+
import { ALL_TOOLS, executeTool as defaultExecuteTool, getToolByName, getToolGateDetails, } from './tools/index.js';
|
|
38
40
|
import { configService } from './config/configService.js';
|
|
41
|
+
import { capabilityForToolName } from './tools/capabilities.js';
|
|
42
|
+
import { createEffectiveToolSet } from './tools/effectiveToolSet.js';
|
|
43
|
+
import { hydrateSessionWorktree } from './tools/worktree/sessionWorktree.js';
|
|
44
|
+
import { capabilitiesForTools, enabledPolicyIds, } from './prompts/policyRegistry.js';
|
|
45
|
+
import { EvidenceLedger } from './harness/evidenceLedger.js';
|
|
46
|
+
import { StallGuard } from './harness/stallGuard.js';
|
|
47
|
+
import { validateFinalClaims } from './harness/claimValidation.js';
|
|
48
|
+
import { createAgentRuntimeContext, } from './runtime/agentRuntimeContext.js';
|
|
49
|
+
const DELIVERY_ARTIFACT = '(?:文件|合同|文档|表格|报告|图片|图像|海报|幻灯片|PPT|PDF|代码|脚本|压缩包|网页|页面|HTML|DOCX|XLSX|CSV|ZIP)';
|
|
50
|
+
const DELIVERY_ACTION = '(?:发|发送|交付|提交|给|传|制作|生成|创建|导出|输出|修改|更新|完成)';
|
|
51
|
+
const ENGLISH_DELIVERY_ARTIFACT = '(?:file|document|report|spreadsheet|workbook|slides?|presentation|pdf|image|code|script|archive|zip|html|webpage|website)';
|
|
52
|
+
const ENGLISH_DELIVERY_ACTION = '(?:send|deliver|submit|provide|create|generate|export|update|modify|finish|produce)';
|
|
53
|
+
/** Whether this turn explicitly asks for a concrete artifact that should be formally delivered. */
|
|
54
|
+
export function expectsFormalDelivery(input) {
|
|
55
|
+
const compact = input.replace(/\s+/g, '');
|
|
56
|
+
const chinese = new RegExp(`${DELIVERY_ACTION}(?:给)?我?.{0,24}${DELIVERY_ARTIFACT}|${DELIVERY_ARTIFACT}.{0,24}${DELIVERY_ACTION}(?:给)?我?`, 'i');
|
|
57
|
+
const english = new RegExp(`\\b${ENGLISH_DELIVERY_ACTION}\\b.{0,80}\\b${ENGLISH_DELIVERY_ARTIFACT}\\b|\\b${ENGLISH_DELIVERY_ARTIFACT}\\b.{0,80}\\b${ENGLISH_DELIVERY_ACTION}\\b`, 'i');
|
|
58
|
+
return chinese.test(compact) || english.test(input);
|
|
59
|
+
}
|
|
60
|
+
const DELIVERY_CORRECTION_REMINDER = `The user requested a concrete final deliverable, but no successful PresentResult call has been made.
|
|
61
|
+
Do not repeat the answer or mention a path only. Call PresentResult now with the actual final files and/or final http(s) links.
|
|
62
|
+
Submit only final outputs, never temporary scripts, unpacked assets, caches, or reference sources. If no valid result exists, explain that honestly without inventing one.`;
|
|
39
63
|
/**
|
|
40
|
-
*
|
|
41
|
-
* 每轮 LLM 请求都会带上整套工具 schema,但 countMessagesTokens 只数历史消息、
|
|
64
|
+
* 每轮 LLM 请求都会带上工具 schema,但 countMessagesTokens 只数历史消息、
|
|
42
65
|
* 漏了这部分 → 系统性低估。autoCompact 判定阈值时加上它才准。
|
|
43
66
|
*
|
|
44
67
|
* ⚠️ 必须懒求值而非模块顶层求值:Agent 工具引入了 tools/index → agent → loop →
|
|
45
68
|
* tools/index 的模块环,若从 tools/index 一侧先进入,顶层求值 ALL_TOOLS 时它还在
|
|
46
69
|
* TDZ(临时死区),启动即 ReferenceError。函数体内访问时模块图早已初始化完毕。
|
|
47
70
|
*/
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
71
|
+
/**
|
|
72
|
+
* 本轮**真正发出去的**工具 schema 估算 token 数。
|
|
73
|
+
*
|
|
74
|
+
* 曾经这里数的是 `ALL_TOOLS`,那是错的:每回合发给模型的是 `activeTools`,
|
|
75
|
+
* 会被 `allowedTools`(工作流步骤 / 子 agent 收窄)、`subagents.enabled`、
|
|
76
|
+
* `computerRun` 是否注入等条件过滤掉一部分。ComputerUse 单个工具的 schema
|
|
77
|
+
* 实测就有 ~5800 token(全量 ~18000 的三分之一),在没暴露它的会话里照样
|
|
78
|
+
* 被计入压缩阈值 → **压缩提前触发**;被 `allowed_tools` 收窄到两三个工具的
|
|
79
|
+
* 子 agent 偏差更大。
|
|
80
|
+
*
|
|
81
|
+
* 缓存必须按工具集**键控**:模块级单值缓存配上按回合变化的工具集,会让所有
|
|
82
|
+
* 后续回合都拿到第一回合那个数字 —— 比不缓存更糟,因为它错得很稳定。
|
|
83
|
+
*/
|
|
84
|
+
const toolsSchemaTokensCache = new Map();
|
|
85
|
+
function getToolsSchemaTokens(tools, signature) {
|
|
86
|
+
const key = signature ?? tools.map((t) => t.name).join(',');
|
|
87
|
+
let cached = toolsSchemaTokensCache.get(key);
|
|
88
|
+
if (cached === undefined) {
|
|
89
|
+
cached = estimateToolsTokens(tools);
|
|
90
|
+
toolsSchemaTokensCache.set(key, cached);
|
|
52
91
|
}
|
|
53
|
-
return
|
|
92
|
+
return cached;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* 顾问咨询配额:基线 1 次,之后每累计 N 次工具调用解锁 1 次,封顶 3 次。
|
|
96
|
+
*
|
|
97
|
+
* 曾经是硬编码 1 次/回合。那个数字隐含「一个用户回合 = 一个决策点」的假设,
|
|
98
|
+
* 在短问答里成立,在「继续,完成后续所有工作」这类一句话跑几十步的长回合里
|
|
99
|
+
* 直接失效 —— 真正有价值的咨询点至少有三个(方案定型前、高风险批次做完后、
|
|
100
|
+
* 收尾汇报前),它们全落在同一个用户回合内,配额只够挑一个:
|
|
101
|
+
* 挑方案阶段就没法收尾复核,挑收尾就等于方案已经错着做完了。
|
|
102
|
+
*
|
|
103
|
+
* 用「已执行的工具调用数」当刻度,而不是轮数或时间:它是本回合真实工作量的
|
|
104
|
+
* 确定性度量,不引入任何模型判断。反向激励可忽略 —— 咨询对模型不是奖励,
|
|
105
|
+
* 刷工具调用换配额的成本远高于收益。
|
|
106
|
+
*/
|
|
107
|
+
const ADVISOR_QUOTA_BASE = 1;
|
|
108
|
+
const ADVISOR_QUOTA_MAX = 3;
|
|
109
|
+
const ADVISOR_QUOTA_UNLOCK_EVERY_TOOL_CALLS = 15;
|
|
110
|
+
export function advisorQuotaFor(executedToolCalls) {
|
|
111
|
+
return Math.min(ADVISOR_QUOTA_MAX, ADVISOR_QUOTA_BASE + Math.floor(executedToolCalls / ADVISOR_QUOTA_UNLOCK_EVERY_TOOL_CALLS));
|
|
54
112
|
}
|
|
55
113
|
/**
|
|
56
114
|
* 执行一次"用户输入 → 模型回答完成"的完整流程。
|
|
@@ -59,31 +117,133 @@ function getToolsSchemaTokens() {
|
|
|
59
117
|
*/
|
|
60
118
|
export async function* runQuery(userInput, options) {
|
|
61
119
|
const { provider, history, signal, sessionState } = options;
|
|
120
|
+
// Tool screenshots are transient evidence for the tool continuation that produced them.
|
|
121
|
+
// Never carry a prior turn's base64 image into a new user request.
|
|
122
|
+
evictPriorVisionShots(history);
|
|
62
123
|
const maxTurns = options.maxTurns ?? 50;
|
|
124
|
+
const runtimeContext = options.runtimeContext ?? createAgentRuntimeContext({
|
|
125
|
+
surface: 'headless',
|
|
126
|
+
profile: 'main',
|
|
127
|
+
cwd: process.cwd(),
|
|
128
|
+
workspaceId: options.memoryContext?.workspaceId,
|
|
129
|
+
sessionId: options.memoryContext?.sessionId,
|
|
130
|
+
owner: options.memoryContext?.access !== 'denied' ? 'local-owner' : 'untrusted',
|
|
131
|
+
memoryAccess: options.memoryContext?.access ?? 'denied',
|
|
132
|
+
interactiveAsk: false,
|
|
133
|
+
browser: options.browserRun,
|
|
134
|
+
computer: options.computerRun,
|
|
135
|
+
computerIntervention: options.computerIntervention,
|
|
136
|
+
toolGate: options.toolGate,
|
|
137
|
+
});
|
|
63
138
|
// DI fallback:测试可传 stub,生产走真实模块单例
|
|
64
139
|
const chatFn = options.chat ?? defaultChat;
|
|
65
140
|
const executeToolFn = options.executeTool ?? defaultExecuteTool;
|
|
141
|
+
const explicitFileDeliveryRequested = expectsFormalDelivery(userInput);
|
|
142
|
+
const transientFor = (tools) => {
|
|
143
|
+
const opaque = options.transientContext?.trim() ?? '';
|
|
144
|
+
const bundle = options.transientBundle;
|
|
145
|
+
if (!bundle)
|
|
146
|
+
return opaque;
|
|
147
|
+
const basePlan = buildContextBudgetPlan({
|
|
148
|
+
provider,
|
|
149
|
+
history,
|
|
150
|
+
tools,
|
|
151
|
+
transientContext: opaque,
|
|
152
|
+
});
|
|
153
|
+
const availableForBundle = Math.max(0, basePlan.inputLimit - basePlan.historyTokens - basePlan.toolTokens - basePlan.transientTokens);
|
|
154
|
+
const selected = availableForBundle >= bundle.diagnostics.usedTokens
|
|
155
|
+
? bundle
|
|
156
|
+
: resizeContextBundle(bundle, availableForBundle);
|
|
157
|
+
return [selected.text, opaque].filter(Boolean).join('\n\n');
|
|
158
|
+
};
|
|
159
|
+
let resultPresented = false;
|
|
160
|
+
let presentedResultUnverified = false;
|
|
161
|
+
let deliveryCorrectionAttempted = false;
|
|
66
162
|
const subagents = await configService.subagents();
|
|
163
|
+
await hydrateSessionWorktree({
|
|
164
|
+
sessionState,
|
|
165
|
+
workspaceId: runtimeContext.workspaceId,
|
|
166
|
+
sessionId: runtimeContext.sessionId,
|
|
167
|
+
cwd: runtimeContext.cwd,
|
|
168
|
+
});
|
|
67
169
|
const toolCallContext = {
|
|
68
170
|
history,
|
|
69
|
-
|
|
171
|
+
sessionState,
|
|
172
|
+
// 配额随本回合工作量增长(见 advisorQuotaFor);下面每轮工具执行完后上调。
|
|
173
|
+
advisorCalls: { used: 0, max: advisorQuotaFor(0) },
|
|
70
174
|
advisorInvocation: 'agent',
|
|
71
175
|
// Agent 等派生工具起嵌套子查询所需的运行态:与主循环同一 provider、
|
|
72
176
|
// 同一审批闸门(子 agent 不绕过宿主策略)。agentCalls 与 advisorCalls 同构,
|
|
73
177
|
// 限同一回合内的子任务派生总数,防失控 fan-out。
|
|
74
178
|
provider,
|
|
75
|
-
toolGate: options.toolGate,
|
|
179
|
+
toolGate: runtimeContext.toolGate ?? options.toolGate,
|
|
76
180
|
agentCalls: { used: 0, max: subagents.enabled ? subagents.maxCallsPerTurn : 0 },
|
|
77
181
|
subagentConfig: subagents,
|
|
78
|
-
browserRun: options.browserRun,
|
|
182
|
+
browserRun: runtimeContext.browser ?? options.browserRun,
|
|
183
|
+
computerRun: runtimeContext.computer ?? options.computerRun,
|
|
184
|
+
computerIntervention: runtimeContext.computerIntervention ?? options.computerIntervention,
|
|
185
|
+
currentUserInput: userInput,
|
|
186
|
+
userMessageId: options.userMessageId,
|
|
187
|
+
memoryAccess: runtimeContext.memoryAccess,
|
|
188
|
+
workspaceId: runtimeContext.workspaceId,
|
|
189
|
+
sessionId: runtimeContext.sessionId,
|
|
190
|
+
cwd: runtimeContext.cwd,
|
|
191
|
+
promptCache: options.promptCache,
|
|
79
192
|
};
|
|
193
|
+
/** 本回合累计执行的工具调用数(顾问配额的刻度,见 advisorQuotaFor)。 */
|
|
194
|
+
let executedToolCalls = 0;
|
|
80
195
|
// 实际下发给模型的工具集:allowedTools 未传时给全量;
|
|
81
196
|
// 传入数组时严格按数组过滤,[] 表示本轮不下发任何工具。
|
|
82
197
|
// 仅影响呈现给 LLM 的工具清单,不影响 executeTool 的内部调度能力。
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
198
|
+
const hostTools = options.availableTools ?? ALL_TOOLS;
|
|
199
|
+
const availableTools = hostTools.filter((tool) => (tool.name !== 'Agent' || subagents.enabled)
|
|
200
|
+
&& (tool.name !== 'ComputerUse' || runtimeContext.computer !== undefined));
|
|
201
|
+
const effectiveToolSet = createEffectiveToolSet({
|
|
202
|
+
availableTools,
|
|
203
|
+
runtime: runtimeContext,
|
|
204
|
+
allowedNames: options.allowedTools,
|
|
205
|
+
requiredNames: options.requiredTools,
|
|
206
|
+
});
|
|
207
|
+
const activeTools = effectiveToolSet.tools;
|
|
208
|
+
const formalDeliveryRequired = explicitFileDeliveryRequested
|
|
209
|
+
&& runtimeContext.profile === 'main'
|
|
210
|
+
&& runtimeContext.surface !== 'workflow'
|
|
211
|
+
&& effectiveToolSet.names.has('PresentResult');
|
|
212
|
+
let currentEffectiveToolSet = effectiveToolSet;
|
|
213
|
+
let lastRequestTransientContext = [options.transientBundle?.text, options.transientContext]
|
|
214
|
+
.filter(Boolean).join('\n\n');
|
|
215
|
+
const evidenceLedger = new EvidenceLedger();
|
|
216
|
+
const stallGuard = new StallGuard();
|
|
217
|
+
const harnessDiagnostics = (stopReason) => ({
|
|
218
|
+
promptVersion: 'minimal-agent:prompt:v2',
|
|
219
|
+
basePromptHash: crypto.createHash('sha256')
|
|
220
|
+
.update(history[0]?.role === 'system' ? history[0].content : '')
|
|
221
|
+
.digest('hex'),
|
|
222
|
+
enabledPolicyIds: enabledPolicyIds({
|
|
223
|
+
profile: runtimeContext.profile,
|
|
224
|
+
surface: runtimeContext.surface,
|
|
225
|
+
cwd: runtimeContext.cwd,
|
|
226
|
+
capabilities: capabilitiesForTools(currentEffectiveToolSet.tools),
|
|
227
|
+
toolNames: currentEffectiveToolSet.names,
|
|
228
|
+
}),
|
|
229
|
+
enabledCapabilities: [...new Set(currentEffectiveToolSet.tools
|
|
230
|
+
.map((tool) => capabilityForToolName(tool.name))
|
|
231
|
+
.filter((capability) => Boolean(capability)))],
|
|
232
|
+
effectiveToolSetSignature: currentEffectiveToolSet.signature,
|
|
233
|
+
tokenBudget: buildContextBudgetPlan({
|
|
234
|
+
provider,
|
|
235
|
+
history,
|
|
236
|
+
tools: currentEffectiveToolSet.tools,
|
|
237
|
+
transientContext: lastRequestTransientContext,
|
|
238
|
+
}),
|
|
239
|
+
stopReason,
|
|
240
|
+
evidence: evidenceLedger.snapshot(),
|
|
241
|
+
});
|
|
86
242
|
let userContent;
|
|
243
|
+
// 行内图片是从用户文本里**猜**出来的(拖拽进终端的路径就走这条路),猜错很正常:
|
|
244
|
+
// 路径可能还没生成、已删除、或纯粹是在讨论一个文件名。这类失败只降级成文本 + 一条
|
|
245
|
+
// warning,绝不能像下面 catch 那样打死整个回合——否则「随口提一个 .png 路径」=回合当场死。
|
|
246
|
+
const skippedInlineImages = [];
|
|
87
247
|
try {
|
|
88
248
|
userContent = await buildUserContent({
|
|
89
249
|
text: userInput,
|
|
@@ -91,9 +251,11 @@ export async function* runQuery(userInput, options) {
|
|
|
91
251
|
parseInlineImages: options.parseInlineImages,
|
|
92
252
|
detail: options.imageDetail,
|
|
93
253
|
maxLongSidePixel: options.imageMaxLongSidePixel,
|
|
254
|
+
onInlineImageSkipped: (source, reason) => skippedInlineImages.push({ source, reason }),
|
|
94
255
|
});
|
|
95
256
|
}
|
|
96
257
|
catch (e) {
|
|
258
|
+
// 走到这里只剩「显式传入的图」读失败(-p --image / 工具实况截图)——调用方明确要了这张图。
|
|
97
259
|
yield {
|
|
98
260
|
type: 'error',
|
|
99
261
|
error: `图片输入处理失败:${e.message}`,
|
|
@@ -101,18 +263,26 @@ export async function* runQuery(userInput, options) {
|
|
|
101
263
|
};
|
|
102
264
|
return;
|
|
103
265
|
}
|
|
266
|
+
for (const skipped of skippedInlineImages) {
|
|
267
|
+
yield {
|
|
268
|
+
type: 'warning',
|
|
269
|
+
message: `未作为图片附加:${skipped.source}(${skipped.reason}),已按普通文本处理。`,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
104
272
|
// 1. 用户消息入栈(v2:填 timestamp,让 MessageList 的 turn 边界横线
|
|
105
273
|
// 能拿到首条 user 的真实时间,否则 fallback Date.now() 会丢失"提交时刻"语义)
|
|
106
|
-
|
|
274
|
+
const userMessage = {
|
|
107
275
|
role: 'user',
|
|
108
276
|
content: userContent,
|
|
109
277
|
...(options.userAttachments?.length ? { attachments: options.userAttachments } : {}),
|
|
278
|
+
...(options.userBrowserReferences?.length ? { browserReferences: options.userBrowserReferences } : {}),
|
|
110
279
|
timestamp: Date.now(),
|
|
111
|
-
id: crypto.randomUUID(),
|
|
112
|
-
}
|
|
280
|
+
id: options.userMessageId ?? crypto.randomUUID(),
|
|
281
|
+
};
|
|
282
|
+
history.push(userMessage);
|
|
113
283
|
// 立即通知 UI:用户消息已入栈,触发 bump 让 <Static> 马上把它 commit 进 scrollback,
|
|
114
284
|
// 不必等整轮 assistant 落定(保证用户输入第一时间置顶,符合 T-A-O-R 展示顺序)。
|
|
115
|
-
yield { type: 'user_message_committed' };
|
|
285
|
+
yield { type: 'user_message_committed', message: userMessage };
|
|
116
286
|
// 反应式压缩自救:本 runQuery 内最多触发 MAX_REACTIVE_PER_QUERY 次(防本轮死循环);
|
|
117
287
|
// 跨 runQuery 的连续失败由 reactiveCompact.ts 的 circuit breaker(consecutiveFailures)兜底。
|
|
118
288
|
let reactiveCount = 0;
|
|
@@ -120,6 +290,12 @@ export async function* runQuery(userInput, options) {
|
|
|
120
290
|
// 上一轮 LLM 返回的真实 prompt token 数(done.promptTokens;provider 开 include_usage 才有)。
|
|
121
291
|
// 作为 autoCompact 的权威判据,校正本地估算的系统性低估;压缩后清空(历史已变,旧值失真)。
|
|
122
292
|
let lastPromptTokens;
|
|
293
|
+
// 提示词缓存观测位(纯展示,不参与任何控制流)。与 lastPromptTokens 同生命周期:
|
|
294
|
+
// 压缩后一并清空,避免把压缩前的命中数据挂到压缩后的新历史上误导排障。
|
|
295
|
+
let lastCachedPromptTokens;
|
|
296
|
+
let lastCacheMissTokens;
|
|
297
|
+
let lastCacheWriteTokens;
|
|
298
|
+
let lastPromptCacheUsage = { reported: false };
|
|
123
299
|
let turn = 0;
|
|
124
300
|
let workStarted = false;
|
|
125
301
|
while (turn < maxTurns) {
|
|
@@ -148,46 +324,166 @@ export async function* runQuery(userInput, options) {
|
|
|
148
324
|
}
|
|
149
325
|
// 2. 自动压缩(判据 = max(上一轮真实 usage, 历史估算 + 工具 schema),
|
|
150
326
|
// 阈值 provider 无关、按 contextWindow × compactRatio 比例触发)
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
// 历史已压缩变小,旧的真实 usage 不再代表当前请求 → 清空,下一轮先用估算判据
|
|
164
|
-
lastPromptTokens = undefined;
|
|
165
|
-
yield { type: 'compact_done', before: compact.before, after: compact.after };
|
|
166
|
-
// 压缩完会回到 LLM 调用,stage 退回 thinking
|
|
167
|
-
yield { type: 'stage_change', stage: 'thinking' };
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
catch (e) {
|
|
171
|
-
// 压缩失败不要让整个对话挂掉:先 snip 砍最老消息释放空间再继续,
|
|
172
|
-
// 避免下一步 LLM 直接撞 prompt-too-long(code='compact_failed' 非致命,不 return)。
|
|
327
|
+
let requestTransientContext = [
|
|
328
|
+
transientFor(activeTools),
|
|
329
|
+
deliveryCorrectionAttempted && !resultPresented ? DELIVERY_CORRECTION_REMINDER : '',
|
|
330
|
+
].filter(Boolean).join('\n\n');
|
|
331
|
+
lastRequestTransientContext = requestTransientContext;
|
|
332
|
+
const preCompactBudget = buildContextBudgetPlan({
|
|
333
|
+
provider,
|
|
334
|
+
history,
|
|
335
|
+
tools: activeTools,
|
|
336
|
+
transientContext: requestTransientContext,
|
|
337
|
+
});
|
|
338
|
+
if (preCompactBudget.overBudget || lastPromptTokens !== undefined) {
|
|
173
339
|
try {
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
340
|
+
const compact = await autoCompactIfNeeded(history, provider, {
|
|
341
|
+
actualPromptTokens: lastPromptTokens,
|
|
342
|
+
toolsTokens: getToolsSchemaTokens(activeTools, effectiveToolSet.signature),
|
|
343
|
+
transientTokens: countTextTokens(requestTransientContext),
|
|
344
|
+
signal,
|
|
345
|
+
promptCache: options.promptCache,
|
|
346
|
+
chat: chatFn,
|
|
347
|
+
});
|
|
348
|
+
if (compact.compacted) {
|
|
349
|
+
yield { type: 'compact_start' };
|
|
350
|
+
yield { type: 'stage_change', stage: 'compacting' };
|
|
177
351
|
history.length = 0;
|
|
178
|
-
history.push(...
|
|
352
|
+
history.push(...compact.messages);
|
|
179
353
|
lastPromptTokens = undefined;
|
|
180
|
-
|
|
354
|
+
lastCachedPromptTokens = undefined;
|
|
355
|
+
lastCacheMissTokens = undefined;
|
|
356
|
+
lastCacheWriteTokens = undefined;
|
|
357
|
+
lastPromptCacheUsage = { reported: false };
|
|
358
|
+
yield {
|
|
359
|
+
type: 'compact_done',
|
|
360
|
+
before: compact.before,
|
|
361
|
+
after: compact.after,
|
|
362
|
+
trigger: 'threshold',
|
|
363
|
+
mode: compact.mode,
|
|
364
|
+
degraded: compact.degraded,
|
|
365
|
+
...(compact.fallbackReason ? { fallbackReason: compact.fallbackReason } : {}),
|
|
366
|
+
...(compact.messagesRemoved !== undefined ? { messagesRemoved: compact.messagesRemoved } : {}),
|
|
367
|
+
...(compact.tokensFreed !== undefined ? { tokensFreed: compact.tokensFreed } : {}),
|
|
368
|
+
};
|
|
369
|
+
yield { type: 'stage_change', stage: 'thinking' };
|
|
181
370
|
}
|
|
182
371
|
}
|
|
183
|
-
catch {
|
|
184
|
-
|
|
372
|
+
catch (e) {
|
|
373
|
+
if (signal?.aborted || e.name === 'AbortError')
|
|
374
|
+
throw e;
|
|
375
|
+
// 压缩失败不要让整个对话挂掉:先 snip 砍最老消息释放空间再继续。
|
|
376
|
+
let recovered = false;
|
|
377
|
+
try {
|
|
378
|
+
const before = countMessagesTokens(history);
|
|
379
|
+
const snipped = snipCompactIfNeeded(history, { force: true, snipPercent: 0.3 });
|
|
380
|
+
if (snipped.messagesRemoved > 0) {
|
|
381
|
+
history.length = 0;
|
|
382
|
+
history.push(...snipped.messages);
|
|
383
|
+
lastPromptTokens = undefined;
|
|
384
|
+
lastCachedPromptTokens = undefined;
|
|
385
|
+
lastCacheMissTokens = undefined;
|
|
386
|
+
lastCacheWriteTokens = undefined;
|
|
387
|
+
lastPromptCacheUsage = { reported: false };
|
|
388
|
+
recovered = true;
|
|
389
|
+
yield {
|
|
390
|
+
type: 'compact_done',
|
|
391
|
+
before,
|
|
392
|
+
after: countMessagesTokens(history),
|
|
393
|
+
trigger: 'threshold',
|
|
394
|
+
mode: 'snip-fallback',
|
|
395
|
+
degraded: true,
|
|
396
|
+
fallbackReason: 'llm-error',
|
|
397
|
+
messagesRemoved: snipped.messagesRemoved,
|
|
398
|
+
tokensFreed: snipped.tokensFreed,
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
catch {
|
|
403
|
+
/* snip 也失败 → 真的只能硬上 */
|
|
404
|
+
}
|
|
405
|
+
if (!recovered) {
|
|
406
|
+
// 事件是 provider 原始报错唯一该去的地方:只到 stderr / UI,不进 history、
|
|
407
|
+
// 不落盘、不会在后续轮次重新发给模型(compact.ts 里那条降级消息因此保持无错误体)。
|
|
408
|
+
yield {
|
|
409
|
+
type: 'error',
|
|
410
|
+
error: `自动压缩失败,规则式截断也未能释放空间;已继续尝试回答:${e.message}`,
|
|
411
|
+
code: 'compact_failed',
|
|
412
|
+
};
|
|
413
|
+
}
|
|
185
414
|
}
|
|
415
|
+
}
|
|
416
|
+
// Re-open transient capacity after compaction. If the compacted tail is still
|
|
417
|
+
// too large, trim old history before sacrificing any model-visible ability.
|
|
418
|
+
requestTransientContext = [
|
|
419
|
+
transientFor(activeTools),
|
|
420
|
+
deliveryCorrectionAttempted && !resultPresented ? DELIVERY_CORRECTION_REMINDER : '',
|
|
421
|
+
].filter(Boolean).join('\n\n');
|
|
422
|
+
lastRequestTransientContext = requestTransientContext;
|
|
423
|
+
let postCompactBudget = buildContextBudgetPlan({
|
|
424
|
+
provider,
|
|
425
|
+
history,
|
|
426
|
+
tools: activeTools,
|
|
427
|
+
transientContext: requestTransientContext,
|
|
428
|
+
});
|
|
429
|
+
if (postCompactBudget.overBudget) {
|
|
430
|
+
const before = countMessagesTokens(history);
|
|
431
|
+
const snipped = snipCompactIfNeeded(history, { force: true, snipPercent: 0.3 });
|
|
432
|
+
if (snipped.messagesRemoved > 0) {
|
|
433
|
+
history.length = 0;
|
|
434
|
+
history.push(...snipped.messages);
|
|
435
|
+
yield {
|
|
436
|
+
type: 'compact_done',
|
|
437
|
+
before,
|
|
438
|
+
after: countMessagesTokens(history),
|
|
439
|
+
trigger: 'post-budget',
|
|
440
|
+
mode: 'snip-fallback',
|
|
441
|
+
degraded: true,
|
|
442
|
+
fallbackReason: 'still-over-budget',
|
|
443
|
+
messagesRemoved: snipped.messagesRemoved,
|
|
444
|
+
tokensFreed: snipped.tokensFreed,
|
|
445
|
+
};
|
|
446
|
+
requestTransientContext = [
|
|
447
|
+
transientFor(activeTools),
|
|
448
|
+
deliveryCorrectionAttempted && !resultPresented ? DELIVERY_CORRECTION_REMINDER : '',
|
|
449
|
+
].filter(Boolean).join('\n\n');
|
|
450
|
+
lastRequestTransientContext = requestTransientContext;
|
|
451
|
+
postCompactBudget = buildContextBudgetPlan({
|
|
452
|
+
provider,
|
|
453
|
+
history,
|
|
454
|
+
tools: activeTools,
|
|
455
|
+
transientContext: requestTransientContext,
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
const emergencyFit = fitToolsForEmergency({
|
|
460
|
+
provider,
|
|
461
|
+
history,
|
|
462
|
+
tools: activeTools,
|
|
463
|
+
transientContext: requestTransientContext,
|
|
464
|
+
requiredNames: effectiveToolSet.requiredNames,
|
|
465
|
+
});
|
|
466
|
+
currentEffectiveToolSet = emergencyFit.removedNames.length === 0
|
|
467
|
+
? effectiveToolSet
|
|
468
|
+
: createEffectiveToolSet({
|
|
469
|
+
availableTools: emergencyFit.tools,
|
|
470
|
+
runtime: runtimeContext,
|
|
471
|
+
requiredNames: [...effectiveToolSet.requiredNames],
|
|
472
|
+
});
|
|
473
|
+
if (emergencyFit.removedNames.length > 0) {
|
|
474
|
+
yield {
|
|
475
|
+
type: 'warning',
|
|
476
|
+
message: `当前模型窗口压力过高,本次请求临时省略工具 Schema:${emergencyFit.removedNames.join(', ')};下一轮会重新评估并恢复。`,
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
if (emergencyFit.plan.overBudget) {
|
|
186
480
|
yield {
|
|
187
481
|
type: 'error',
|
|
188
|
-
error:
|
|
189
|
-
code: '
|
|
482
|
+
error: `当前请求即使经过上下文降级仍需要约 ${emergencyFit.plan.totalInputTokens} tokens,超过模型输入上限 ${emergencyFit.plan.inputLimit}。已停止发送,避免 Provider 接收必然失败的超限请求。`,
|
|
483
|
+
code: 'context_overflow',
|
|
190
484
|
};
|
|
485
|
+
yield { type: 'harness_diagnostics', diagnostics: harnessDiagnostics('error') };
|
|
486
|
+
return;
|
|
191
487
|
}
|
|
192
488
|
// 3. 调 LLM 并组装 assistant 消息
|
|
193
489
|
let assistantText = '';
|
|
@@ -212,17 +508,13 @@ export async function* runQuery(userInput, options) {
|
|
|
212
508
|
yield { type: 'stage_change', stage: 'thinking' };
|
|
213
509
|
workStarted = true;
|
|
214
510
|
try {
|
|
215
|
-
const modelMessages =
|
|
216
|
-
? [
|
|
217
|
-
{ role: 'system', content: options.transientContext.trim() },
|
|
218
|
-
...history,
|
|
219
|
-
]
|
|
220
|
-
: history;
|
|
511
|
+
const modelMessages = buildModelMessages(history, requestTransientContext, userMessage.id);
|
|
221
512
|
for await (const ev of chatFn({
|
|
222
513
|
provider,
|
|
223
514
|
messages: modelMessages,
|
|
224
|
-
tools:
|
|
515
|
+
tools: currentEffectiveToolSet.tools,
|
|
225
516
|
signal,
|
|
517
|
+
promptCache: options.promptCache,
|
|
226
518
|
})) {
|
|
227
519
|
if (ev.type === 'text_delta') {
|
|
228
520
|
if (!stageStreamingYielded) {
|
|
@@ -255,6 +547,15 @@ export async function* runQuery(userInput, options) {
|
|
|
255
547
|
// 真实 prompt token(provider 开 include_usage 才有)→ 喂给下一轮 autoCompact 做权威判据
|
|
256
548
|
if (typeof ev.promptTokens === 'number')
|
|
257
549
|
lastPromptTokens = ev.promptTokens;
|
|
550
|
+
// 缓存命中观测:只在 provider 真实报告时才记录(undefined ≠ 0,见 types.ts 注释)
|
|
551
|
+
if (typeof ev.cachedPromptTokens === 'number')
|
|
552
|
+
lastCachedPromptTokens = ev.cachedPromptTokens;
|
|
553
|
+
if (typeof ev.cacheMissTokens === 'number')
|
|
554
|
+
lastCacheMissTokens = ev.cacheMissTokens;
|
|
555
|
+
if (typeof ev.cacheWriteTokens === 'number')
|
|
556
|
+
lastCacheWriteTokens = ev.cacheWriteTokens;
|
|
557
|
+
if (ev.promptCacheUsage)
|
|
558
|
+
lastPromptCacheUsage = ev.promptCacheUsage;
|
|
258
559
|
}
|
|
259
560
|
}
|
|
260
561
|
}
|
|
@@ -277,17 +578,27 @@ export async function* runQuery(userInput, options) {
|
|
|
277
578
|
reactiveCount++;
|
|
278
579
|
yield { type: 'compact_start' };
|
|
279
580
|
yield { type: 'stage_change', stage: 'compacting' };
|
|
280
|
-
const result = await reactiveCompactIfApplicable(history, provider, e, sessionState?.reactive, signal);
|
|
581
|
+
const result = await reactiveCompactIfApplicable(history, provider, e, sessionState?.reactive, signal, {}, options.promptCache);
|
|
281
582
|
// 仅当确实压下去了(after < before)才重试;否则(没压动 / 未恢复)退回错误路径,杜绝死循环。
|
|
282
583
|
const shrank = (result.after ?? 0) < (result.before ?? 0);
|
|
283
584
|
if (result.recovered && shrank) {
|
|
284
585
|
history.length = 0;
|
|
285
586
|
history.push(...result.messages);
|
|
286
587
|
lastPromptTokens = undefined; // 历史已变小,旧真实 usage 失真
|
|
588
|
+
lastCachedPromptTokens = undefined;
|
|
589
|
+
lastCacheMissTokens = undefined;
|
|
590
|
+
lastCacheWriteTokens = undefined;
|
|
591
|
+
lastPromptCacheUsage = { reported: false };
|
|
287
592
|
yield {
|
|
288
593
|
type: 'compact_done',
|
|
289
594
|
before: result.before ?? 0,
|
|
290
595
|
after: result.after ?? 0,
|
|
596
|
+
trigger: 'provider-overflow',
|
|
597
|
+
mode: result.mode,
|
|
598
|
+
degraded: result.degraded,
|
|
599
|
+
...(result.fallbackReason ? { fallbackReason: result.fallbackReason } : {}),
|
|
600
|
+
...(result.messagesRemoved !== undefined ? { messagesRemoved: result.messagesRemoved } : {}),
|
|
601
|
+
...(result.tokensFreed !== undefined ? { tokensFreed: result.tokensFreed } : {}),
|
|
291
602
|
};
|
|
292
603
|
// 压缩完回到 LLM 调用
|
|
293
604
|
yield { type: 'stage_change', stage: 'thinking' };
|
|
@@ -338,6 +649,16 @@ export async function* runQuery(userInput, options) {
|
|
|
338
649
|
yield { type: 'assistant_message', message: assistantMsg };
|
|
339
650
|
// 4. 没有工具调用 → 整轮交互结束
|
|
340
651
|
if (toolCallsByIndex.length === 0) {
|
|
652
|
+
// A model occasionally writes a path/link in prose and stops without formally
|
|
653
|
+
// presenting it. Give it one model-only corrective pass; the reminder is
|
|
654
|
+
// transient and therefore never pollutes persisted conversation history.
|
|
655
|
+
if (formalDeliveryRequired
|
|
656
|
+
&& !resultPresented
|
|
657
|
+
&& !deliveryCorrectionAttempted
|
|
658
|
+
&& turn < maxTurns) {
|
|
659
|
+
deliveryCorrectionAttempted = true;
|
|
660
|
+
continue;
|
|
661
|
+
}
|
|
341
662
|
// R2:最终回答被 provider 因 max_tokens 截断(finish_reason='length')时,
|
|
342
663
|
// 不能静默当正常 end_turn 收尾 —— 否则 -p 宿主会拿到半截答案却以为成功。
|
|
343
664
|
// 走通用 warning(text/json 都落 stderr,不污染 json 那一行 stdout 契约)。
|
|
@@ -348,8 +669,15 @@ export async function* runQuery(userInput, options) {
|
|
|
348
669
|
'可调大模型的 max_tokens 上限,或让模型分段输出。',
|
|
349
670
|
};
|
|
350
671
|
}
|
|
351
|
-
|
|
672
|
+
// The final answer has already consumed the latest screenshot. Persist only a compact
|
|
673
|
+
// marker so the next turn does not resend a large screen image.
|
|
674
|
+
evictPriorVisionShots(history);
|
|
675
|
+
const estimatedTokens = countMessagesTokens(history) + getToolsSchemaTokens(activeTools);
|
|
352
676
|
const usedTokens = Math.min(provider.contextWindow, Math.max(lastPromptTokens ?? 0, estimatedTokens));
|
|
677
|
+
yield { type: 'harness_diagnostics', diagnostics: harnessDiagnostics('complete') };
|
|
678
|
+
const claimValidation = validateFinalClaims(assistantText, evidenceLedger.snapshot());
|
|
679
|
+
for (const warning of claimValidation.warnings)
|
|
680
|
+
yield { type: 'warning', message: warning };
|
|
353
681
|
yield {
|
|
354
682
|
type: 'context_usage',
|
|
355
683
|
usedTokens,
|
|
@@ -357,8 +685,39 @@ export async function* runQuery(userInput, options) {
|
|
|
357
685
|
remainingTokens: Math.max(0, provider.contextWindow - usedTokens),
|
|
358
686
|
compactThreshold: getCompactThreshold(provider),
|
|
359
687
|
source: lastPromptTokens === undefined ? 'estimated' : 'actual',
|
|
688
|
+
// 缓存观测只在有真实 usage 时才附带——估算路径没有缓存信息,
|
|
689
|
+
// 不能拿估算值冒充命中率(见 types.ts 的 context_usage 注释)。
|
|
690
|
+
...(lastPromptTokens !== undefined && lastCachedPromptTokens !== undefined
|
|
691
|
+
? { cachedPromptTokens: lastCachedPromptTokens }
|
|
692
|
+
: {}),
|
|
693
|
+
...(lastPromptTokens !== undefined && lastCacheWriteTokens !== undefined
|
|
694
|
+
? { cacheWriteTokens: lastCacheWriteTokens }
|
|
695
|
+
: {}),
|
|
696
|
+
...(lastPromptTokens !== undefined && lastCacheMissTokens !== undefined
|
|
697
|
+
? { cacheMissTokens: lastCacheMissTokens }
|
|
698
|
+
: {}),
|
|
699
|
+
promptCacheUsage: lastPromptCacheUsage,
|
|
360
700
|
};
|
|
361
|
-
|
|
701
|
+
if (formalDeliveryRequired && !resultPresented) {
|
|
702
|
+
const warning = '交付未完成:用户明确要求提供文件,但本轮没有成功提交可打开的文件结果。';
|
|
703
|
+
yield { type: 'warning', message: warning };
|
|
704
|
+
yield { type: 'turn_done', completionStatus: 'delivery_incomplete', warning };
|
|
705
|
+
}
|
|
706
|
+
else if (resultPresented && presentedResultUnverified) {
|
|
707
|
+
const warning = '结果已提交,但至少一个文件尚未通过结构化验证。';
|
|
708
|
+
yield { type: 'warning', message: warning };
|
|
709
|
+
yield { type: 'turn_done', completionStatus: 'delivery_unverified', warning };
|
|
710
|
+
}
|
|
711
|
+
else if (!claimValidation.verified) {
|
|
712
|
+
yield {
|
|
713
|
+
type: 'turn_done',
|
|
714
|
+
completionStatus: 'claims_unverified',
|
|
715
|
+
warning: claimValidation.warnings.join('\n'),
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
else {
|
|
719
|
+
yield { type: 'turn_done', completionStatus: 'completed' };
|
|
720
|
+
}
|
|
362
721
|
return;
|
|
363
722
|
}
|
|
364
723
|
// 5. 执行工具调用
|
|
@@ -375,13 +734,49 @@ export async function* runQuery(userInput, options) {
|
|
|
375
734
|
// 执行单个 tool call → WorkerOutcome(内部吞掉 abort/异常,永不 reject)。
|
|
376
735
|
const runOne = async (tc, reportProgress) => {
|
|
377
736
|
try {
|
|
737
|
+
if (stallGuard.shouldReject(tc)) {
|
|
738
|
+
return {
|
|
739
|
+
tc,
|
|
740
|
+
result: {
|
|
741
|
+
ok: false,
|
|
742
|
+
error: '检测到同一工具和参数已连续两次返回相同失败;第三次调用已在执行前拒绝,请改变输入或改用其他路径。',
|
|
743
|
+
recovery: 'change-input',
|
|
744
|
+
sensitivity: 'public',
|
|
745
|
+
},
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
// The model-visible catalog and the execution boundary are the same set.
|
|
749
|
+
// A provider may still emit a stale or fabricated tool call; return a
|
|
750
|
+
// recoverable result so the model can choose an available path.
|
|
751
|
+
if (!currentEffectiveToolSet.names.has(tc.function.name)) {
|
|
752
|
+
const deniedReason = tc.function.name === 'Agent'
|
|
753
|
+
? '当前上下文不允许调用 Agent;请直接完成子任务或改用已提供的工具'
|
|
754
|
+
: `工具 ${tc.function.name} 不在当前宿主授权的有效工具集中,请改用已提供的工具继续任务`;
|
|
755
|
+
return {
|
|
756
|
+
tc,
|
|
757
|
+
result: {
|
|
758
|
+
ok: false,
|
|
759
|
+
error: deniedReason,
|
|
760
|
+
recovery: 'change-input',
|
|
761
|
+
sensitivity: 'public',
|
|
762
|
+
},
|
|
763
|
+
};
|
|
764
|
+
}
|
|
378
765
|
// 工具执行闸门:宿主注入后覆盖所有工具,包括只读工具。deny → 把 reason 当
|
|
379
766
|
// tool_result 回填,模型据此改道/如实报告,不抛异常不中断整轮。宿主异常一律 deny。
|
|
380
767
|
const isReadOnly = getToolByName(tc.function.name)?.isReadOnly === true;
|
|
381
|
-
|
|
768
|
+
const effectiveToolGate = runtimeContext.toolGate ?? options.toolGate;
|
|
769
|
+
if (effectiveToolGate) {
|
|
382
770
|
let decision;
|
|
383
771
|
try {
|
|
384
|
-
|
|
772
|
+
const gateDetails = getToolGateDetails(tc.function.name, tc.function.arguments, { ...toolCallContext, cwd: sessionState?.activeWorktree?.path ?? toolCallContext.cwd, toolCallId: tc.id, reportProgress });
|
|
773
|
+
decision = await effectiveToolGate({
|
|
774
|
+
name: tc.function.name,
|
|
775
|
+
argsJson: tc.function.arguments,
|
|
776
|
+
isReadOnly,
|
|
777
|
+
toolCallId: tc.id,
|
|
778
|
+
...gateDetails,
|
|
779
|
+
}, signal);
|
|
385
780
|
}
|
|
386
781
|
catch {
|
|
387
782
|
decision = { allow: false, reason: '工具执行审批失败(宿主异常)' };
|
|
@@ -393,7 +788,27 @@ export async function* runQuery(userInput, options) {
|
|
|
393
788
|
options.onToolStart?.(tc.function.name, tc.id);
|
|
394
789
|
// 浅拷贝填入本次调用的 toolCallId:进度按调用实例路由(并行同名工具不串扰)。
|
|
395
790
|
// advisorCalls/agentCalls 是共享对象引用,计数仍全轮共享,不受拷贝影响。
|
|
396
|
-
|
|
791
|
+
let result = await executeToolFn(tc.function.name, tc.function.arguments, signal, { ...toolCallContext, cwd: sessionState?.activeWorktree?.path ?? toolCallContext.cwd, toolCallId: tc.id, reportProgress });
|
|
792
|
+
if (tc.function.name === 'PresentResult'
|
|
793
|
+
&& result.ok
|
|
794
|
+
&& result.metadata?.kind === 'result_manifest'
|
|
795
|
+
&& options.commitFormalDelivery) {
|
|
796
|
+
try {
|
|
797
|
+
await options.commitFormalDelivery({
|
|
798
|
+
toolCallId: tc.id,
|
|
799
|
+
turnId: userMessage.id ?? options.userMessageId ?? 'turn:unknown',
|
|
800
|
+
manifest: result.metadata.manifest,
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
catch (error) {
|
|
804
|
+
result = {
|
|
805
|
+
ok: false,
|
|
806
|
+
error: `正式交付未能可靠保存:${error.message}`,
|
|
807
|
+
recovery: 'retry',
|
|
808
|
+
sensitivity: 'local',
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
}
|
|
397
812
|
options.onToolEnd?.(tc.id, false);
|
|
398
813
|
return { tc, result };
|
|
399
814
|
}
|
|
@@ -504,11 +919,19 @@ export async function* runQuery(userInput, options) {
|
|
|
504
919
|
settled.push(o);
|
|
505
920
|
}
|
|
506
921
|
}
|
|
922
|
+
// 本回合工作量累计 → 上调顾问配额(只增不减;已用掉的次数不会因此回退)。
|
|
923
|
+
executedToolCalls += settled.length;
|
|
924
|
+
toolCallContext.advisorCalls.max = advisorQuotaFor(executedToolCalls);
|
|
507
925
|
// 严格按 toolCallsByIndex 顺序把 tool 消息回填 history(settled 已保持该顺序)。
|
|
508
926
|
let anyAborted = false;
|
|
509
927
|
for (const { tc, result } of settled) {
|
|
928
|
+
evidenceLedger.record(tc.function.name, tc.function.arguments, result);
|
|
510
929
|
if (!result.ok && result.error === 'aborted')
|
|
511
930
|
anyAborted = true;
|
|
931
|
+
if (tc.function.name === 'PresentResult' && result.ok && result.metadata?.kind === 'result_manifest') {
|
|
932
|
+
resultPresented = true;
|
|
933
|
+
presentedResultUnverified ||= result.metadata.manifest.files.some((file) => file.verification !== 'passed');
|
|
934
|
+
}
|
|
512
935
|
const rawContent = result.ok ? result.content : `Error: ${result.error}`;
|
|
513
936
|
// 微压缩仅对成功结果走(失败结果通常很短,不需要压缩)
|
|
514
937
|
const content = result.ok
|
|
@@ -523,6 +946,40 @@ export async function* runQuery(userInput, options) {
|
|
|
523
946
|
id: crypto.randomUUID(),
|
|
524
947
|
});
|
|
525
948
|
}
|
|
949
|
+
// If the third identical observation is also the configured final turn, retain the
|
|
950
|
+
// established max_turns termination contract. The stall guard is valuable only when
|
|
951
|
+
// it can stop a failing path before the caller's explicit turn budget does.
|
|
952
|
+
const preemptedRepeatedFailure = settled.some(({ result }) => !result.ok && result.error.includes('第三次调用已在执行前拒绝'));
|
|
953
|
+
const stall = preemptedRepeatedFailure
|
|
954
|
+
? ({
|
|
955
|
+
stalled: true,
|
|
956
|
+
reason: 'repeated_failure',
|
|
957
|
+
})
|
|
958
|
+
: turn + 1 < maxTurns
|
|
959
|
+
? stallGuard.observe(settled.map((item) => item.tc), settled.map((item) => item.result), assistantText)
|
|
960
|
+
: { stalled: false };
|
|
961
|
+
if (stall.stalled) {
|
|
962
|
+
const message = stall.reason === 'repeated_failure'
|
|
963
|
+
? '检测到同一工具以相同参数连续返回相同失败,已停止重复重试。'
|
|
964
|
+
: '检测到连续工具轮次没有产生新文本或新结果,已停止无进展循环。';
|
|
965
|
+
yield { type: 'warning', message };
|
|
966
|
+
yield { type: 'harness_diagnostics', diagnostics: harnessDiagnostics('stalled') };
|
|
967
|
+
yield { type: 'error', error: message, code: 'stalled' };
|
|
968
|
+
return;
|
|
969
|
+
}
|
|
970
|
+
// 关键节点截图回填:把工具附带的实况截图作为一条带图 user 消息喂给(视觉)模型自我核验,
|
|
971
|
+
// 并 yield injected_message 让宿主在对话里即时展示这张实况截图。
|
|
972
|
+
// 纯增强:读图/编码等任何失败都静默跳过,绝不打断本轮(与工具「失败不抛异常」一致)。
|
|
973
|
+
let injectedShot = null;
|
|
974
|
+
try {
|
|
975
|
+
injectedShot = await injectVisionFollowups(history, settled, provider);
|
|
976
|
+
}
|
|
977
|
+
catch { /* 截图回填失败不影响主流程 */ }
|
|
978
|
+
// The history copy is intentionally downgraded after the turn. Emit a detached snapshot
|
|
979
|
+
// so TUI/Desktop stores do not see their already-rendered image mutate underneath them.
|
|
980
|
+
if (injectedShot) {
|
|
981
|
+
yield { type: 'injected_message', message: structuredClone(injectedShot) };
|
|
982
|
+
}
|
|
526
983
|
if (anyAborted || signal?.aborted) {
|
|
527
984
|
if (options.hasQueuedInput?.()) {
|
|
528
985
|
pushQueuedInputMarker(history);
|
|
@@ -542,6 +999,7 @@ export async function* runQuery(userInput, options) {
|
|
|
542
999
|
// 6. 继续 while 让模型看到 tool_result 后继续推理
|
|
543
1000
|
}
|
|
544
1001
|
// code='max_turns':撞最大轮数上限而终结,stop_reason='max_turns'
|
|
1002
|
+
yield { type: 'harness_diagnostics', diagnostics: harnessDiagnostics('max_turns') };
|
|
545
1003
|
yield {
|
|
546
1004
|
type: 'error',
|
|
547
1005
|
error: `达到最大轮数 ${maxTurns},提前结束(防止失控)。如果合理可以提高 maxTurns。`,
|
|
@@ -549,6 +1007,99 @@ export async function* runQuery(userInput, options) {
|
|
|
549
1007
|
};
|
|
550
1008
|
}
|
|
551
1009
|
// ---------------- 辅助函数 ----------------
|
|
1010
|
+
/**
|
|
1011
|
+
* 易变上下文只存在于 provider 请求副本中,不会进入会话历史。
|
|
1012
|
+
*/
|
|
1013
|
+
export const SYSTEM_REMINDER_OPEN = '<system-reminder>';
|
|
1014
|
+
export const SYSTEM_REMINDER_CLOSE = '</system-reminder>';
|
|
1015
|
+
/**
|
|
1016
|
+
* Attach transient context to the current turn's user message in a model-only
|
|
1017
|
+
* clone. Later tool-loop requests reuse the same decorated message, making each
|
|
1018
|
+
* serialized request a strict prefix extension of the previous one.
|
|
1019
|
+
*/
|
|
1020
|
+
export function buildModelMessages(history, transientContext, currentUserMessageId) {
|
|
1021
|
+
const transient = transientContext?.trim();
|
|
1022
|
+
if (!transient)
|
|
1023
|
+
return history;
|
|
1024
|
+
let index = currentUserMessageId
|
|
1025
|
+
? history.findIndex((message) => message.role === 'user' && message.id === currentUserMessageId)
|
|
1026
|
+
: -1;
|
|
1027
|
+
if (index < 0) {
|
|
1028
|
+
for (let cursor = history.length - 1; cursor >= 0; cursor--) {
|
|
1029
|
+
if (history[cursor]?.role === 'user') {
|
|
1030
|
+
index = cursor;
|
|
1031
|
+
break;
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
if (index < 0)
|
|
1036
|
+
return history;
|
|
1037
|
+
const target = history[index];
|
|
1038
|
+
if (target.role !== 'user')
|
|
1039
|
+
return history;
|
|
1040
|
+
const reminder = `${SYSTEM_REMINDER_OPEN}\n${transient}\n${SYSTEM_REMINDER_CLOSE}`;
|
|
1041
|
+
const content = typeof target.content === 'string'
|
|
1042
|
+
? `${target.content}\n\n${reminder}`
|
|
1043
|
+
: [...target.content, { type: 'text', text: reminder }];
|
|
1044
|
+
const next = [...history];
|
|
1045
|
+
next[index] = { ...target, content };
|
|
1046
|
+
return next;
|
|
1047
|
+
}
|
|
1048
|
+
// 关键节点图像核验的合成消息标记:既是给模型的提示前缀,也是 evictPriorVisionShots 识别旧图消息的锚。
|
|
1049
|
+
export const VISION_SHOT_MARKER = '【实况图像】';
|
|
1050
|
+
/**
|
|
1051
|
+
* 历史标记。这套机制最初只有 WebBrowser 用得上,文案写死了「浏览器」;
|
|
1052
|
+
* 现在 Bash 的 ATTACH_IMAGE sentinel 也能产图(屏幕截图 / 生成的产物),文案必须中性。
|
|
1053
|
+
* 但**持久化会话里仍存着旧前缀的消息**,淘汰逻辑必须继续认它 ——
|
|
1054
|
+
* 否则老会话里的旧截图永远不会被降级,图片一路累积撑爆上下文。
|
|
1055
|
+
*/
|
|
1056
|
+
const LEGACY_VISION_SHOT_MARKERS = ['【浏览器实况截图】'];
|
|
1057
|
+
const VISION_SHOT_PROMPT = `${VISION_SHOT_MARKER}上面是刚才工具执行后附带的真实图像(浏览器页面 / 屏幕截图 / 生成的产物)。请仔细看图核对:是否真的到达/呈现了你预期的状态?若与预期不符(空白/报错/弹窗遮挡/验证拦截/内容不对),不要假设成功,如实说明并调整下一步。`;
|
|
1058
|
+
/** 一条 user 消息的文本是否是「实况图像」合成消息(含历史前缀)。 */
|
|
1059
|
+
function isVisionShotText(text) {
|
|
1060
|
+
return (text.startsWith(VISION_SHOT_MARKER) ||
|
|
1061
|
+
LEGACY_VISION_SHOT_MARKERS.some((marker) => text.startsWith(marker)));
|
|
1062
|
+
}
|
|
1063
|
+
/**
|
|
1064
|
+
* 把本轮工具附带的实况截图(ToolResult.visionImages)组装成一条带图 user 消息喂给模型自我核验。
|
|
1065
|
+
* - 仅在模型明确支持识图时注入(provider.supportsVision === true);否则丢弃(不假装核验、也不给非视觉端点发图触发 400)。
|
|
1066
|
+
* - 注入前先把历史里旧的截图消息降级为纯文字占位(evictPriorVisionShots),保证上下文里最多一张实况图,
|
|
1067
|
+
* 避免图片累积撑爆上下文(呼应浏览器 ADR「高频帧只存内存、只持久化最终结果」)。
|
|
1068
|
+
* - **失败的工具结果同样回填**:出错现场那张图往往比成功时更有信息量(权限弹窗、
|
|
1069
|
+
* 验证拦截、意外的前台窗口)。此前这里要求 `ok === true`,逼得想附图的工具只能
|
|
1070
|
+
* 把失败伪装成成功——见 types.ts 里 ToolResult 那段注释。
|
|
1071
|
+
*/
|
|
1072
|
+
async function injectVisionFollowups(history, settled, provider) {
|
|
1073
|
+
if (provider.supportsVision !== true)
|
|
1074
|
+
return null;
|
|
1075
|
+
const images = settled.flatMap((s) => s.result.visionImages ?? []);
|
|
1076
|
+
if (images.length === 0)
|
|
1077
|
+
return null;
|
|
1078
|
+
evictPriorVisionShots(history);
|
|
1079
|
+
// 工具可以给自己这批图配一句说明(visionNote)。内核只负责把它拼在通用提示之后,
|
|
1080
|
+
// 不解读内容——「这是一组 1Hz 连续帧」这类语义只有产图的工具知道。
|
|
1081
|
+
const notes = settled
|
|
1082
|
+
.flatMap((s) => (s.result.visionNote ? [s.result.visionNote] : []))
|
|
1083
|
+
.filter((note, index, all) => all.indexOf(note) === index);
|
|
1084
|
+
const text = notes.length > 0
|
|
1085
|
+
? `${VISION_SHOT_PROMPT}\n\n${notes.join('\n\n')}`
|
|
1086
|
+
: VISION_SHOT_PROMPT;
|
|
1087
|
+
const content = await buildUserContent({ text, images });
|
|
1088
|
+
const msg = { role: 'user', content, timestamp: Date.now(), id: crypto.randomUUID() };
|
|
1089
|
+
history.push(msg);
|
|
1090
|
+
return msg;
|
|
1091
|
+
}
|
|
1092
|
+
/** 把历史里旧的实况图像 user 消息降级为纯文字占位、剔除其图片,释放上下文(只保留最近一张真图)。 */
|
|
1093
|
+
function evictPriorVisionShots(history) {
|
|
1094
|
+
for (const msg of history) {
|
|
1095
|
+
if (msg.role !== 'user' || !Array.isArray(msg.content))
|
|
1096
|
+
continue;
|
|
1097
|
+
const textPart = msg.content.find((p) => p.type === 'text');
|
|
1098
|
+
if (textPart?.type === 'text' && isVisionShotText(textPart.text)) {
|
|
1099
|
+
msg.content = `${VISION_SHOT_MARKER}(此前的实况图像已从上下文移除以节省空间。)`;
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
552
1103
|
/**
|
|
553
1104
|
* 用户按ESC手动中断
|
|
554
1105
|
*/
|