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,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ============================================================
|
|
3
|
+
* src/context/sessionPath.ts —— 按目录隔离的会话文件路径
|
|
4
|
+
* ------------------------------------------------------------
|
|
5
|
+
* 做的事:
|
|
6
|
+
* 新会话使用 Workspace Index 的稳定 workspaceId,写入
|
|
7
|
+
* ~/.minimal-agent/sessions/workspace-<id>--<sessionId>.json。
|
|
8
|
+
* 因此外接盘挂载名、盘符或 symlink 表达变化时仍指向同一份历史。
|
|
9
|
+
*
|
|
10
|
+
* 旧命名规则(路径索引时代,仍用于兼容迁移):
|
|
11
|
+
* <sanitized-path>-<6-hex-hash>--<sessionId>.json
|
|
12
|
+
* sanitized:路径归一化后只保留 [a-z0-9-],最长 80 字符
|
|
13
|
+
* hash :原始路径 sha1 前 6 位,防 sanitize 后碰撞
|
|
14
|
+
* sessionId:v2 多 session 支持;默认 'default'
|
|
15
|
+
*
|
|
16
|
+
* resolveSessionFile() 会验证当前路径及 Workspace Index 中的历史别名,
|
|
17
|
+
* 首次打开旧文件时复制到稳定文件名;旧文件保留作为恢复依据。
|
|
18
|
+
*
|
|
19
|
+
* 迁移:
|
|
20
|
+
* 旧版 ~/.minimal-agent/last-context.json 启动时一次性 rename 到
|
|
21
|
+
* 当前 cwd 对应的会话文件。失败静默——丢迁移好过崩进程。
|
|
22
|
+
* ============================================================
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { createHash } from 'node:crypto';
|
|
26
|
+
import { constants, readFileSync, realpathSync } from 'node:fs';
|
|
27
|
+
import { chmod, copyFile, mkdir, rename, stat } from 'node:fs/promises';
|
|
28
|
+
import { dirname, join, resolve } from 'node:path';
|
|
29
|
+
import { agentStateDir } from './agentHome.js';
|
|
30
|
+
|
|
31
|
+
const DEFAULT_SESSION_ID = 'default';
|
|
32
|
+
const SESSION_ID_RE = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,79}$/;
|
|
33
|
+
|
|
34
|
+
interface IndexedWorkspaceIdentity {
|
|
35
|
+
id: string;
|
|
36
|
+
canonicalPath: string;
|
|
37
|
+
displayPath?: string;
|
|
38
|
+
pathAliases?: string[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function comparablePath(path: string): string {
|
|
42
|
+
try { return realpathSync(resolve(path)).replace(/\\/g, '/').toLowerCase(); }
|
|
43
|
+
catch { return resolve(path).replace(/\\/g, '/').toLowerCase(); }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function mountStablePath(path: string): string {
|
|
47
|
+
return comparablePath(path).replace(/^\/volumes\/([^/]+?) \d+(\/|$)/, '/volumes/$1$2');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function indexedWorkspaceFor(cwd: string): IndexedWorkspaceIdentity | null {
|
|
51
|
+
try {
|
|
52
|
+
const parsed = JSON.parse(readFileSync(join(agentStateDir(), 'workspace-index.json'), 'utf8')) as {
|
|
53
|
+
workspaces?: IndexedWorkspaceIdentity[];
|
|
54
|
+
};
|
|
55
|
+
const target = comparablePath(cwd);
|
|
56
|
+
const mountTarget = mountStablePath(cwd);
|
|
57
|
+
return parsed.workspaces?.find((workspace) => [
|
|
58
|
+
workspace.canonicalPath,
|
|
59
|
+
workspace.displayPath,
|
|
60
|
+
...(workspace.pathAliases ?? []),
|
|
61
|
+
].some((path) => typeof path === 'string'
|
|
62
|
+
&& (comparablePath(path) === target || mountStablePath(path) === mountTarget))) ?? null;
|
|
63
|
+
} catch {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function stableSessionFileFor(workspaceId: string, sessionId: string): string {
|
|
69
|
+
const safeWorkspaceId = workspaceId.replace(/[^a-zA-Z0-9._-]/g, '');
|
|
70
|
+
if (!safeWorkspaceId) throw new Error('workspace id 无效');
|
|
71
|
+
return join(agentStateDir(), 'sessions', `workspace-${safeWorkspaceId}--${validateSessionId(sessionId)}.json`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** 仅用于旧历史识别和迁移;新调用应使用 sessionFileFor。 */
|
|
75
|
+
export function legacyPathSessionFileFor(cwd: string, sessionId: string = DEFAULT_SESSION_ID): string {
|
|
76
|
+
const safeSessionId = validateSessionId(sessionId);
|
|
77
|
+
const normalized = resolve(cwd).replace(/\\/g, '/').toLowerCase();
|
|
78
|
+
const sanitized = normalized
|
|
79
|
+
.replace(/:/g, '')
|
|
80
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
81
|
+
.replace(/^-+|-+$/g, '')
|
|
82
|
+
.slice(0, 80);
|
|
83
|
+
const hash = createHash('sha1').update(normalized).digest('hex').slice(0, 6);
|
|
84
|
+
return join(agentStateDir(), 'sessions', `${sanitized}-${hash}--${safeSessionId}.json`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function validateSessionId(sessionId: string): string {
|
|
88
|
+
const value = sessionId.trim();
|
|
89
|
+
if (!SESSION_ID_RE.test(value) || value === '.' || value === '..' || value.includes('..')) {
|
|
90
|
+
throw new Error('session id 只能包含字母、数字、点、下划线和短横线,且长度不超过 80');
|
|
91
|
+
}
|
|
92
|
+
return value;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* 把 cwd + sessionId 编码成会话文件绝对路径。
|
|
97
|
+
*
|
|
98
|
+
* @param cwd 工作目录
|
|
99
|
+
* @param sessionId 多 session 支持的 id;缺省 'default'
|
|
100
|
+
*
|
|
101
|
+
* 注意:'default' 调用走新格式 `<base>--default.json`。如果磁盘上只有
|
|
102
|
+
* 旧格式 `<base>.json`(v1 历史文件),需要用 resolveSessionFile()
|
|
103
|
+
* 拿到实际存在的路径用于 load。
|
|
104
|
+
*/
|
|
105
|
+
export function sessionFileFor(
|
|
106
|
+
cwd: string,
|
|
107
|
+
sessionId: string = DEFAULT_SESSION_ID,
|
|
108
|
+
): string {
|
|
109
|
+
const safeSessionId = validateSessionId(sessionId);
|
|
110
|
+
const workspace = indexedWorkspaceFor(cwd);
|
|
111
|
+
return workspace
|
|
112
|
+
? stableSessionFileFor(workspace.id, safeSessionId)
|
|
113
|
+
: legacyPathSessionFileFor(cwd, safeSessionId);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** 旧格式(v1,不带 `--<id>` 后缀)—— 仅 'default' session 可能存在 */
|
|
117
|
+
function legacySessionFileFor(cwd: string): string {
|
|
118
|
+
const normalized = resolve(cwd).replace(/\\/g, '/').toLowerCase();
|
|
119
|
+
const sanitized = normalized
|
|
120
|
+
.replace(/:/g, '')
|
|
121
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
122
|
+
.replace(/^-+|-+$/g, '')
|
|
123
|
+
.slice(0, 80);
|
|
124
|
+
const hash = createHash('sha1').update(normalized).digest('hex').slice(0, 6);
|
|
125
|
+
return join(
|
|
126
|
+
agentStateDir(),
|
|
127
|
+
'sessions',
|
|
128
|
+
`${sanitized}-${hash}.json`,
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* 解析 cwd + sessionId 对应的实际存在的会话文件路径。
|
|
134
|
+
*
|
|
135
|
+
* - 新格式(带 `--<id>`)存在 → 返回新路径
|
|
136
|
+
* - 仅 sessionId='default' 且新路径不存在但旧路径(不带 `--`)存在 → 返回旧路径
|
|
137
|
+
* - 都不存在 → 返回新路径(让调用方按新格式 create)
|
|
138
|
+
*/
|
|
139
|
+
export async function resolveSessionFile(
|
|
140
|
+
cwd: string,
|
|
141
|
+
sessionId: string = DEFAULT_SESSION_ID,
|
|
142
|
+
): Promise<string> {
|
|
143
|
+
const fresh = sessionFileFor(cwd, sessionId);
|
|
144
|
+
try {
|
|
145
|
+
await stat(fresh);
|
|
146
|
+
return fresh;
|
|
147
|
+
} catch {
|
|
148
|
+
// 新路径不存在
|
|
149
|
+
}
|
|
150
|
+
const workspace = indexedWorkspaceFor(cwd);
|
|
151
|
+
const aliases = workspace
|
|
152
|
+
? [cwd, workspace.canonicalPath, workspace.displayPath, ...(workspace.pathAliases ?? [])]
|
|
153
|
+
: [cwd];
|
|
154
|
+
const candidates = [...new Set(aliases.filter((value): value is string => typeof value === 'string'))]
|
|
155
|
+
.flatMap((path) => [
|
|
156
|
+
legacyPathSessionFileFor(path, sessionId),
|
|
157
|
+
...(sessionId === DEFAULT_SESSION_ID ? [legacySessionFileFor(path)] : []),
|
|
158
|
+
]);
|
|
159
|
+
for (const legacy of candidates) {
|
|
160
|
+
try {
|
|
161
|
+
await stat(legacy);
|
|
162
|
+
} catch {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
if (legacy !== fresh && workspace) {
|
|
166
|
+
try {
|
|
167
|
+
await mkdir(dirname(fresh), { recursive: true });
|
|
168
|
+
await copyFile(legacy, fresh, constants.COPYFILE_EXCL).catch(async (error) => {
|
|
169
|
+
if ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error;
|
|
170
|
+
});
|
|
171
|
+
await chmod(fresh, 0o600).catch(() => {});
|
|
172
|
+
return fresh;
|
|
173
|
+
} catch {
|
|
174
|
+
// Migration is an optimization. The verified legacy history remains
|
|
175
|
+
// authoritative when the stable destination cannot be written.
|
|
176
|
+
return legacy;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return legacy;
|
|
180
|
+
}
|
|
181
|
+
return fresh;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* 一次性把旧版 ~/.minimal-agent/last-context.json 迁到当前 cwd 对应的
|
|
186
|
+
* 会话文件。
|
|
187
|
+
*
|
|
188
|
+
* 静默策略:
|
|
189
|
+
* - 旧文件不存在 → 直接返回
|
|
190
|
+
* - rename 失败(目标已存在 / 权限等)→ 忽略,旧文件原位等下次启动
|
|
191
|
+
*/
|
|
192
|
+
export async function migrateLegacyContext(cwd: string): Promise<void> {
|
|
193
|
+
const legacy = join(agentStateDir(), 'last-context.json');
|
|
194
|
+
try {
|
|
195
|
+
await stat(legacy);
|
|
196
|
+
} catch {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
const target = sessionFileFor(cwd);
|
|
200
|
+
try {
|
|
201
|
+
await mkdir(dirname(target), { recursive: true });
|
|
202
|
+
await rename(legacy, target);
|
|
203
|
+
} catch {
|
|
204
|
+
// 静默
|
|
205
|
+
}
|
|
206
|
+
}
|
|
@@ -273,7 +273,7 @@ export async function listSessionSummaries(cwd) {
|
|
|
273
273
|
if (!ids.includes(session.id))
|
|
274
274
|
ids.push(session.id);
|
|
275
275
|
}
|
|
276
|
-
return Promise.all(ids.map(async (id) => {
|
|
276
|
+
return (await Promise.all(ids.map(async (id) => {
|
|
277
277
|
const existing = group[id] ?? indexedById.get(id);
|
|
278
278
|
let modified = existing?.updatedAt ?? Date.now();
|
|
279
279
|
try {
|
|
@@ -288,7 +288,13 @@ export async function listSessionSummaries(cwd) {
|
|
|
288
288
|
createdAt: modified,
|
|
289
289
|
updatedAt: modified,
|
|
290
290
|
};
|
|
291
|
-
}))
|
|
291
|
+
}))).sort((a, b) => {
|
|
292
|
+
if (a.id === 'default')
|
|
293
|
+
return -1;
|
|
294
|
+
if (b.id === 'default')
|
|
295
|
+
return 1;
|
|
296
|
+
return b.updatedAt - a.updatedAt;
|
|
297
|
+
});
|
|
292
298
|
}
|
|
293
299
|
async function createSessionUnlocked(cwd, options) {
|
|
294
300
|
const id = validateSessionId(options.id ?? randomUUID());
|
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ============================================================
|
|
3
|
+
* src/context/sessionRegistry.ts —— 同 cwd 多 session 注册表
|
|
4
|
+
* ------------------------------------------------------------
|
|
5
|
+
* 做的事:
|
|
6
|
+
* 一个工作目录下可以有多个并行 session(id = 任意字符串),
|
|
7
|
+
* 每次只激活一个。active 信息存 ~/.minimal-agent/active-sessions.json,
|
|
8
|
+
* 键是 cwd 的 sha1[:12],值是当前激活的 session id 字符串。
|
|
9
|
+
*
|
|
10
|
+
* 文件路径:
|
|
11
|
+
* <MINIMAL_AGENT_HOME ?? ~>/.minimal-agent/active-sessions.json
|
|
12
|
+
* <MINIMAL_AGENT_HOME ?? ~>/.minimal-agent/sessions/<cwd-hash>--<id>.json
|
|
13
|
+
* (新格式;旧格式 <cwd-hash>.json 视作 id='default' 兼容)
|
|
14
|
+
*
|
|
15
|
+
* 抉择:
|
|
16
|
+
* - active 文件 atomic write(写 .tmp + rename)—— 防止跨进程并发写时部分写入
|
|
17
|
+
* - sessionId 为字符串,'default' 是约定的默认值(无 active 时返回它)
|
|
18
|
+
* - listSessions 同时扫新旧格式,保证迁移期不丢
|
|
19
|
+
* ============================================================
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
23
|
+
import { basename } from 'node:path';
|
|
24
|
+
import { copyFile, mkdir, readFile, readdir, rename, rm, stat, unlink, writeFile } from 'node:fs/promises';
|
|
25
|
+
import { homedir } from 'node:os';
|
|
26
|
+
import { join } from 'node:path';
|
|
27
|
+
import { resolveSessionFile, sessionFileFor, validateSessionId } from './sessionPath.js';
|
|
28
|
+
import { workspaceIndexService } from './workspaceIndex.js';
|
|
29
|
+
export { validateSessionId } from './sessionPath.js';
|
|
30
|
+
|
|
31
|
+
/** 解析根目录,支持 MINIMAL_AGENT_HOME 测试覆盖 */
|
|
32
|
+
function agentHome(): string {
|
|
33
|
+
return process.env.MINIMAL_AGENT_HOME ?? homedir();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function agentDir(): string {
|
|
37
|
+
return join(agentHome(), '.minimal-agent');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function activeFilePath(): string {
|
|
41
|
+
return join(agentDir(), 'active-sessions.json');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function sessionsDirPath(): string {
|
|
45
|
+
return join(agentDir(), 'sessions');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function cwdHash(cwd: string): string {
|
|
49
|
+
return createHash('sha1').update(cwd).digest('hex').slice(0, 12);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
interface ActiveMap {
|
|
53
|
+
[cwdHash: string]: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface SessionSummary {
|
|
57
|
+
id: string;
|
|
58
|
+
title: string;
|
|
59
|
+
createdAt: number;
|
|
60
|
+
updatedAt: number;
|
|
61
|
+
lastMessagePreview?: string;
|
|
62
|
+
/** Session 级模型绑定;缺省时回退到全局基座模型。 */
|
|
63
|
+
modelId?: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface SessionMetadataFile {
|
|
67
|
+
[cwdHash: string]: Record<string, SessionSummary>;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function metadataFilePath(): string {
|
|
71
|
+
return join(agentDir(), 'session-metadata.json');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function loadMetadata(): Promise<SessionMetadataFile> {
|
|
75
|
+
try {
|
|
76
|
+
const value = JSON.parse(await readFile(metadataFilePath(), 'utf8')) as unknown;
|
|
77
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value as SessionMetadataFile : {};
|
|
78
|
+
} catch {
|
|
79
|
+
return {};
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function saveMetadata(value: SessionMetadataFile): Promise<void> {
|
|
84
|
+
await mkdir(agentDir(), { recursive: true });
|
|
85
|
+
const target = metadataFilePath();
|
|
86
|
+
const tmp = `${target}.${process.pid}.${randomUUID()}.tmp`;
|
|
87
|
+
try {
|
|
88
|
+
await writeFile(tmp, JSON.stringify(value, null, 2), { encoding: 'utf8', mode: 0o600 });
|
|
89
|
+
await rename(tmp, target);
|
|
90
|
+
} finally {
|
|
91
|
+
await unlink(tmp).catch(() => {});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function loadActiveMap(): Promise<ActiveMap> {
|
|
96
|
+
try {
|
|
97
|
+
const raw = await readFile(activeFilePath(), 'utf8');
|
|
98
|
+
const data = JSON.parse(raw);
|
|
99
|
+
if (data && typeof data === 'object' && !Array.isArray(data)) {
|
|
100
|
+
return data as ActiveMap;
|
|
101
|
+
}
|
|
102
|
+
return {};
|
|
103
|
+
} catch {
|
|
104
|
+
return {};
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function saveActiveMap(map: ActiveMap): Promise<void> {
|
|
109
|
+
await mkdir(agentDir(), { recursive: true });
|
|
110
|
+
const target = activeFilePath();
|
|
111
|
+
const tmp = `${target}.${process.pid}.${randomUUID()}.tmp`;
|
|
112
|
+
try {
|
|
113
|
+
await writeFile(tmp, JSON.stringify(map), { encoding: 'utf8', mode: 0o600 });
|
|
114
|
+
await rename(tmp, target);
|
|
115
|
+
} finally {
|
|
116
|
+
await unlink(tmp).catch(() => {});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* 同进程内的串行互斥队列(`session-metadata.json` / `active-sessions.json` 都是全局
|
|
122
|
+
* 单文件,按 cwdHash 分组——两个并发调用方 load→改→save 会互相覆盖丢更新)。
|
|
123
|
+
* 照抄 `plugins/cron-runner/src/store.ts` 的 `enqueueMutation` 模式。
|
|
124
|
+
*
|
|
125
|
+
* 同进程先排队,队首操作再取得跨进程目录锁。CLI、Desktop Runtime 与 Cron 可以同时
|
|
126
|
+
* 使用同一个 MINIMAL_AGENT_HOME;仅靠 rename 只能避免半写文件,不能避免两个进程
|
|
127
|
+
* load→改→save 时后写覆盖先写。目录创建在各平台都是原子的,适合做短临界区互斥。
|
|
128
|
+
*
|
|
129
|
+
* 下面所有导出的可变函数(setActiveSession/createSession/updateSessionMetadata/
|
|
130
|
+
* copySession/deleteSession)都必须整体包一层 enqueueMutation;内部若需要调用
|
|
131
|
+
* 另一个可变操作的逻辑,必须调用其 *Unlocked 版本而不是导出版本——否则内层调用会
|
|
132
|
+
* 排到同一队列的外层调用之后,外层等内层、内层等外层排到自己,死锁。
|
|
133
|
+
*/
|
|
134
|
+
let mutationQueue: Promise<void> = Promise.resolve();
|
|
135
|
+
const LOCK_STALE_MS = 120_000;
|
|
136
|
+
const LOCK_WAIT_MS = 15_000;
|
|
137
|
+
|
|
138
|
+
function lockDirPath(): string {
|
|
139
|
+
return join(agentDir(), 'session-registry.lock');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function processIsAlive(pid: number): boolean {
|
|
143
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
144
|
+
try {
|
|
145
|
+
process.kill(pid, 0);
|
|
146
|
+
return true;
|
|
147
|
+
} catch (error) {
|
|
148
|
+
return (error as NodeJS.ErrnoException).code === 'EPERM';
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async function acquireRegistryLock(): Promise<() => Promise<void>> {
|
|
153
|
+
const lockDir = lockDirPath();
|
|
154
|
+
const ownerFile = join(lockDir, 'owner.json');
|
|
155
|
+
const startedAt = Date.now();
|
|
156
|
+
await mkdir(agentDir(), { recursive: true, mode: 0o700 });
|
|
157
|
+
while (true) {
|
|
158
|
+
try {
|
|
159
|
+
await mkdir(lockDir, { mode: 0o700 });
|
|
160
|
+
await writeFile(ownerFile, JSON.stringify({ pid: process.pid, createdAt: Date.now() }), {
|
|
161
|
+
encoding: 'utf8',
|
|
162
|
+
mode: 0o600,
|
|
163
|
+
});
|
|
164
|
+
return async () => { await rm(lockDir, { recursive: true, force: true }); };
|
|
165
|
+
} catch (error) {
|
|
166
|
+
if ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error;
|
|
167
|
+
let stale = false;
|
|
168
|
+
try {
|
|
169
|
+
const [ownerRaw, info] = await Promise.all([readFile(ownerFile, 'utf8'), stat(lockDir)]);
|
|
170
|
+
const owner = JSON.parse(ownerRaw) as { pid?: number; createdAt?: number };
|
|
171
|
+
stale = typeof owner.pid === 'number'
|
|
172
|
+
? !processIsAlive(owner.pid)
|
|
173
|
+
: Date.now() - info.mtimeMs > LOCK_STALE_MS;
|
|
174
|
+
} catch {
|
|
175
|
+
try {
|
|
176
|
+
stale = Date.now() - (await stat(lockDir)).mtimeMs > LOCK_STALE_MS;
|
|
177
|
+
} catch {
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (stale) {
|
|
182
|
+
await rm(lockDir, { recursive: true, force: true });
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
if (Date.now() - startedAt > LOCK_WAIT_MS) {
|
|
186
|
+
throw new Error('等待 Session 注册表锁超时,请确认没有异常残留的 minimal-agent 进程');
|
|
187
|
+
}
|
|
188
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function enqueueMutation<T>(mutate: () => Promise<T>): Promise<T> {
|
|
194
|
+
const run = async (): Promise<T> => {
|
|
195
|
+
const release = await acquireRegistryLock();
|
|
196
|
+
try {
|
|
197
|
+
return await mutate();
|
|
198
|
+
} finally {
|
|
199
|
+
await release();
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
const operation = mutationQueue.then(run, run);
|
|
203
|
+
mutationQueue = operation.then(() => undefined, () => undefined);
|
|
204
|
+
return operation;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* 获取当前 cwd 的激活 session id。
|
|
209
|
+
* 无记录 → 返回 'default'。
|
|
210
|
+
*/
|
|
211
|
+
export async function getActiveSession(cwd: string): Promise<string> {
|
|
212
|
+
const workspace = await workspaceIndexService.findByPath(cwd).catch(() => null);
|
|
213
|
+
if (workspace?.activeSessionId) {
|
|
214
|
+
try { return validateSessionId(workspace.activeSessionId); } catch { /* fall through */ }
|
|
215
|
+
}
|
|
216
|
+
const map = await loadActiveMap();
|
|
217
|
+
try { return validateSessionId(map[cwdHash(cwd)] ?? 'default'); }
|
|
218
|
+
catch { return 'default'; }
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async function setActiveSessionUnlocked(cwd: string, sessionId: string): Promise<void> {
|
|
222
|
+
const id = validateSessionId(sessionId);
|
|
223
|
+
const map = await loadActiveMap();
|
|
224
|
+
map[cwdHash(cwd)] = id;
|
|
225
|
+
await saveActiveMap(map);
|
|
226
|
+
// 索引是自愈便利层:权威存储(active-sessions.json)已落盘,索引写失败
|
|
227
|
+
// (锁超时/磁盘错误)绝不能反过来打断会话切换——尽力而为,下次 reconcile 自愈。
|
|
228
|
+
await workspaceIndexService.setActive(cwd, id).catch(async () => {
|
|
229
|
+
await workspaceIndexService.recordPendingMutation({ type: 'active', path: cwd, sessionId: id }).catch(() => {});
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* 设置当前 cwd 的激活 session id。
|
|
235
|
+
*
|
|
236
|
+
* 写入策略:先写 <target>.tmp,再原子 rename 到 target;
|
|
237
|
+
* 即使被中断也只会留临时文件,不破坏 target。
|
|
238
|
+
*/
|
|
239
|
+
export async function setActiveSession(
|
|
240
|
+
cwd: string,
|
|
241
|
+
sessionId: string,
|
|
242
|
+
): Promise<void> {
|
|
243
|
+
return enqueueMutation(() => setActiveSessionUnlocked(cwd, sessionId));
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* 列出当前 cwd 下所有真实存在的 session id。
|
|
248
|
+
*
|
|
249
|
+
* 扫描规则:
|
|
250
|
+
* - 新格式 `<hash>--<id>.json` → 提取 id
|
|
251
|
+
* - 旧格式 `<hash>.json` → 视作 'default'
|
|
252
|
+
* - default 只有存在历史文件或元数据时才返回;CLI 的隐式 default 写入兼容不变
|
|
253
|
+
*/
|
|
254
|
+
export async function listSessions(cwd: string): Promise<string[]> {
|
|
255
|
+
const dir = sessionsDirPath();
|
|
256
|
+
const defaultName = basename(sessionFileFor(cwd, 'default'));
|
|
257
|
+
const prefix = defaultName.slice(0, -'default.json'.length);
|
|
258
|
+
let names: string[] = [];
|
|
259
|
+
try { names = await readdir(dir); } catch { /* a new workspace has no files yet */ }
|
|
260
|
+
const ids = names
|
|
261
|
+
.filter((name) => name.startsWith(prefix) && name.endsWith('.json') && !name.endsWith('.tmp.json'))
|
|
262
|
+
.map((name) => name.slice(prefix.length, -'.json'.length))
|
|
263
|
+
.filter((id) => {
|
|
264
|
+
try { validateSessionId(id); return true; } catch { return false; }
|
|
265
|
+
});
|
|
266
|
+
const metadata = await loadMetadata();
|
|
267
|
+
ids.push(...Object.keys(metadata[cwdHash(cwd)] ?? {}).filter((id) => !ids.includes(id)));
|
|
268
|
+
if (!ids.includes('default')) {
|
|
269
|
+
try {
|
|
270
|
+
await stat(await resolveSessionFile(cwd, 'default'));
|
|
271
|
+
ids.push('default');
|
|
272
|
+
} catch {
|
|
273
|
+
// A missing implicit default is not a visible conversation.
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
const unique = [...new Set(ids.filter((id) => id !== 'default'))].sort();
|
|
277
|
+
return ids.includes('default') ? ['default', ...unique] : unique;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export async function listSessionSummaries(cwd: string): Promise<SessionSummary[]> {
|
|
281
|
+
const ids = await listSessions(cwd);
|
|
282
|
+
const metadata = await loadMetadata();
|
|
283
|
+
const group = metadata[cwdHash(cwd)] ?? {};
|
|
284
|
+
const indexed = await workspaceIndexService.findByPath(cwd).catch(() => null);
|
|
285
|
+
const indexedById = new Map((indexed?.sessions ?? []).map((session) => [session.id, session]));
|
|
286
|
+
for (const session of indexed?.sessions ?? []) {
|
|
287
|
+
if (!ids.includes(session.id)) ids.push(session.id);
|
|
288
|
+
}
|
|
289
|
+
return (await Promise.all(ids.map(async (id) => {
|
|
290
|
+
const existing = group[id] ?? indexedById.get(id);
|
|
291
|
+
let modified = existing?.updatedAt ?? Date.now();
|
|
292
|
+
try { modified = (await stat(await resolveSessionFile(cwd, id))).mtimeMs; } catch { /* metadata-only draft */ }
|
|
293
|
+
return existing
|
|
294
|
+
? { ...existing, updatedAt: Math.max(existing.updatedAt, modified) }
|
|
295
|
+
: {
|
|
296
|
+
id,
|
|
297
|
+
title: id === 'default' ? '默认会话' : id,
|
|
298
|
+
createdAt: modified,
|
|
299
|
+
updatedAt: modified,
|
|
300
|
+
};
|
|
301
|
+
}))).sort((a, b) => {
|
|
302
|
+
if (a.id === 'default') return -1;
|
|
303
|
+
if (b.id === 'default') return 1;
|
|
304
|
+
return b.updatedAt - a.updatedAt;
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
async function createSessionUnlocked(
|
|
309
|
+
cwd: string,
|
|
310
|
+
options: { id?: string; title?: string; modelId?: string },
|
|
311
|
+
): Promise<SessionSummary> {
|
|
312
|
+
const id = validateSessionId(options.id ?? randomUUID());
|
|
313
|
+
const metadata = await loadMetadata();
|
|
314
|
+
const hash = cwdHash(cwd);
|
|
315
|
+
const group = metadata[hash] ?? {};
|
|
316
|
+
if (group[id] || (await listSessions(cwd)).includes(id)) throw new Error(`session 已存在: ${id}`);
|
|
317
|
+
const now = Date.now();
|
|
318
|
+
const summary = {
|
|
319
|
+
id,
|
|
320
|
+
title: options.title?.trim() || '新会话',
|
|
321
|
+
createdAt: now,
|
|
322
|
+
updatedAt: now,
|
|
323
|
+
...(options.modelId?.trim() ? { modelId: options.modelId.trim() } : {}),
|
|
324
|
+
};
|
|
325
|
+
metadata[hash] = { ...group, [id]: summary };
|
|
326
|
+
await saveMetadata(metadata);
|
|
327
|
+
// 权威元数据已落盘;索引只是便利层,失败不阻断建会话(见 setActiveSessionUnlocked)。
|
|
328
|
+
await workspaceIndexService.touchSession(cwd, summary).catch(async () => {
|
|
329
|
+
await workspaceIndexService.recordPendingMutation({ type: 'touch', path: cwd, session: summary }).catch(() => {});
|
|
330
|
+
});
|
|
331
|
+
return summary;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export async function createSession(
|
|
335
|
+
cwd: string,
|
|
336
|
+
options: { id?: string; title?: string; modelId?: string } = {},
|
|
337
|
+
): Promise<SessionSummary> {
|
|
338
|
+
return enqueueMutation(() => createSessionUnlocked(cwd, options));
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
async function updateSessionMetadataUnlocked(
|
|
342
|
+
cwd: string,
|
|
343
|
+
sessionId: string,
|
|
344
|
+
patch: { title?: string; lastMessagePreview?: string; modelId?: string | null },
|
|
345
|
+
): Promise<SessionSummary> {
|
|
346
|
+
const id = validateSessionId(sessionId);
|
|
347
|
+
const metadata = await loadMetadata();
|
|
348
|
+
const hash = cwdHash(cwd);
|
|
349
|
+
const group = metadata[hash] ?? {};
|
|
350
|
+
const now = Date.now();
|
|
351
|
+
const previous = group[id] ?? { id, title: id === 'default' ? '默认会话' : id, createdAt: now, updatedAt: now };
|
|
352
|
+
const next = {
|
|
353
|
+
...previous,
|
|
354
|
+
...(patch.title !== undefined ? { title: patch.title.trim() || previous.title } : {}),
|
|
355
|
+
...(patch.lastMessagePreview !== undefined ? { lastMessagePreview: patch.lastMessagePreview.slice(0, 240) } : {}),
|
|
356
|
+
...(patch.modelId === null ? { modelId: undefined } : patch.modelId !== undefined ? { modelId: patch.modelId.trim() } : {}),
|
|
357
|
+
updatedAt: now,
|
|
358
|
+
};
|
|
359
|
+
metadata[hash] = { ...group, [id]: next };
|
|
360
|
+
await saveMetadata(metadata);
|
|
361
|
+
// 权威元数据已落盘;索引只是便利层,失败不阻断元数据更新。
|
|
362
|
+
await workspaceIndexService.touchSession(cwd, next).catch(async () => {
|
|
363
|
+
await workspaceIndexService.recordPendingMutation({ type: 'touch', path: cwd, session: next }).catch(() => {});
|
|
364
|
+
});
|
|
365
|
+
return next;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
export async function updateSessionMetadata(
|
|
369
|
+
cwd: string,
|
|
370
|
+
sessionId: string,
|
|
371
|
+
patch: { title?: string; lastMessagePreview?: string; modelId?: string | null },
|
|
372
|
+
): Promise<SessionSummary> {
|
|
373
|
+
return enqueueMutation(() => updateSessionMetadataUnlocked(cwd, sessionId, patch));
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
export async function copySession(cwd: string, sourceSessionId: string, title?: string): Promise<SessionSummary> {
|
|
377
|
+
return enqueueMutation(async () => {
|
|
378
|
+
const sourceId = validateSessionId(sourceSessionId);
|
|
379
|
+
const source = (await listSessionSummaries(cwd)).find((session) => session.id === sourceId);
|
|
380
|
+
const target = await createSessionUnlocked(cwd, { title: title ?? '会话副本', ...(source?.modelId ? { modelId: source.modelId } : {}) });
|
|
381
|
+
try {
|
|
382
|
+
await mkdir(sessionsDirPath(), { recursive: true });
|
|
383
|
+
await copyFile(await resolveSessionFile(cwd, sourceId), sessionFileFor(cwd, target.id));
|
|
384
|
+
} catch {
|
|
385
|
+
// An empty source session is still valid; its metadata is the useful clone.
|
|
386
|
+
}
|
|
387
|
+
return target;
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
export async function deleteSession(cwd: string, sessionId: string): Promise<void> {
|
|
392
|
+
return enqueueMutation(async () => {
|
|
393
|
+
const id = validateSessionId(sessionId);
|
|
394
|
+
if (id === 'default') throw new Error('默认会话不能删除,可以归档后清空');
|
|
395
|
+
await unlink(await resolveSessionFile(cwd, id)).catch(() => {});
|
|
396
|
+
const metadata = await loadMetadata();
|
|
397
|
+
const hash = cwdHash(cwd);
|
|
398
|
+
if (metadata[hash]) {
|
|
399
|
+
delete metadata[hash][id];
|
|
400
|
+
await saveMetadata(metadata);
|
|
401
|
+
}
|
|
402
|
+
// 权威元数据已删除;索引只是便利层,失败不阻断删会话。
|
|
403
|
+
await workspaceIndexService.removeSession(cwd, id).catch(async () => {
|
|
404
|
+
await workspaceIndexService.recordPendingMutation({ type: 'remove-session', path: cwd, sessionId: id }).catch(() => {});
|
|
405
|
+
});
|
|
406
|
+
// 注意:这里故意直接读 loadActiveMap + 调 *Unlocked,不调导出的
|
|
407
|
+
// getActiveSession/setActiveSession——同一个已在队列里的 mutation
|
|
408
|
+
// 不能再排一次同一队列,否则自己等自己,死锁。
|
|
409
|
+
const activeMap = await loadActiveMap();
|
|
410
|
+
if ((activeMap[cwdHash(cwd)] ?? 'default') === id) {
|
|
411
|
+
await setActiveSessionUnlocked(cwd, 'default');
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
}
|