patchwork-os 0.2.0-beta.1 → 0.2.0-beta.10.canary.100
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.bridge.md +14 -14
- package/README.md +201 -34
- package/deploy/README.md +1 -1
- package/deploy/bootstrap-vps.sh +14 -9
- package/deploy/deploy-dashboard.sh +25 -1
- package/deploy/macos/README.md +153 -0
- package/deploy/macos/com.patchwork.bridge.plist.template +54 -0
- package/deploy/macos/com.patchwork.tunnel.plist.template +76 -0
- package/deploy/macos/install-mac-bridge.sh +244 -0
- package/deploy/macos/uninstall-mac-bridge.sh +22 -0
- package/dist/activityLog.d.ts +6 -0
- package/dist/activityLog.js +63 -26
- package/dist/activityLog.js.map +1 -1
- package/dist/adapters/claude.js +22 -16
- package/dist/adapters/claude.js.map +1 -1
- package/dist/adapters/gemini.js +15 -11
- package/dist/adapters/gemini.js.map +1 -1
- package/dist/adapters/openai.js +9 -9
- package/dist/adapters/openai.js.map +1 -1
- package/dist/ajv2020.d.ts +25 -0
- package/dist/ajv2020.js +33 -0
- package/dist/ajv2020.js.map +1 -0
- package/dist/analyticsAggregator.js +1 -1
- package/dist/analyticsAggregator.js.map +1 -1
- package/dist/analyticsConfig.d.ts +29 -0
- package/dist/analyticsConfig.js +102 -0
- package/dist/analyticsConfig.js.map +1 -0
- package/dist/analyticsPrefs.d.ts +38 -2
- package/dist/analyticsPrefs.js +148 -23
- package/dist/analyticsPrefs.js.map +1 -1
- package/dist/analyticsSend.d.ts +17 -1
- package/dist/analyticsSend.js +67 -5
- package/dist/analyticsSend.js.map +1 -1
- package/dist/approvalHttp.d.ts +14 -0
- package/dist/approvalHttp.js +212 -9
- package/dist/approvalHttp.js.map +1 -1
- package/dist/approvalInsights.js +13 -5
- package/dist/approvalInsights.js.map +1 -1
- package/dist/approvalQueue.d.ts +97 -3
- package/dist/approvalQueue.js +193 -19
- package/dist/approvalQueue.js.map +1 -1
- package/dist/approvalSignals.js +19 -11
- package/dist/approvalSignals.js.map +1 -1
- package/dist/automation.d.ts +35 -10
- package/dist/automation.js +133 -37
- package/dist/automation.js.map +1 -1
- package/dist/automationSuggestions.js +10 -8
- package/dist/automationSuggestions.js.map +1 -1
- package/dist/bridge.d.ts +2 -0
- package/dist/bridge.js +219 -35
- package/dist/bridge.js.map +1 -1
- package/dist/bridgeLockDiscovery.d.ts +27 -1
- package/dist/bridgeLockDiscovery.js +38 -11
- package/dist/bridgeLockDiscovery.js.map +1 -1
- package/dist/bridgeToken.js +3 -2
- package/dist/bridgeToken.js.map +1 -1
- package/dist/claudeDriver.js +23 -8
- package/dist/claudeDriver.js.map +1 -1
- package/dist/claudeOrchestrator.d.ts +1 -1
- package/dist/claudeOrchestrator.js +87 -44
- package/dist/claudeOrchestrator.js.map +1 -1
- package/dist/commands/analytics.d.ts +8 -0
- package/dist/commands/analytics.js +134 -0
- package/dist/commands/analytics.js.map +1 -0
- package/dist/commands/auditEnv.d.ts +36 -0
- package/dist/commands/auditEnv.js +202 -0
- package/dist/commands/auditEnv.js.map +1 -0
- package/dist/commands/connect.d.ts +47 -0
- package/dist/commands/connect.js +419 -0
- package/dist/commands/connect.js.map +1 -0
- package/dist/commands/dashboard.js +8 -1
- package/dist/commands/dashboard.js.map +1 -1
- package/dist/commands/doctor.d.ts +35 -0
- package/dist/commands/doctor.js +51 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/install.js +3 -0
- package/dist/commands/install.js.map +1 -1
- package/dist/commands/launchd.js +15 -16
- package/dist/commands/launchd.js.map +1 -1
- package/dist/commands/patchworkInit.d.ts +5 -0
- package/dist/commands/patchworkInit.js +89 -7
- package/dist/commands/patchworkInit.js.map +1 -1
- package/dist/commands/recipe.d.ts +151 -0
- package/dist/commands/recipe.js +649 -9
- package/dist/commands/recipe.js.map +1 -1
- package/dist/commands/recipeInstall.js +11 -4
- package/dist/commands/recipeInstall.js.map +1 -1
- package/dist/commands/shadowScan.d.ts +34 -0
- package/dist/commands/shadowScan.js +142 -0
- package/dist/commands/shadowScan.js.map +1 -0
- package/dist/commands/task.d.ts +25 -0
- package/dist/commands/task.js +62 -42
- package/dist/commands/task.js.map +1 -1
- package/dist/commands/tools.d.ts +20 -1
- package/dist/commands/tools.js +112 -3
- package/dist/commands/tools.js.map +1 -1
- package/dist/commands/tracesExport.d.ts +15 -1
- package/dist/commands/tracesExport.js +39 -5
- package/dist/commands/tracesExport.js.map +1 -1
- package/dist/commands/tracesImport.js +21 -4
- package/dist/commands/tracesImport.js.map +1 -1
- package/dist/commitIssueLinkLog.d.ts +16 -0
- package/dist/commitIssueLinkLog.js +110 -24
- package/dist/commitIssueLinkLog.js.map +1 -1
- package/dist/companions/registry.js +15 -7
- package/dist/companions/registry.js.map +1 -1
- package/dist/config.d.ts +47 -3
- package/dist/config.js +111 -21
- package/dist/config.js.map +1 -1
- package/dist/connectorRoutes.js +1422 -57
- package/dist/connectorRoutes.js.map +1 -1
- package/dist/connectors/airtable.d.ts +230 -0
- package/dist/connectors/airtable.js +700 -0
- package/dist/connectors/airtable.js.map +1 -0
- package/dist/connectors/asana.js +13 -9
- package/dist/connectors/asana.js.map +1 -1
- package/dist/connectors/baseConnector.js +25 -3
- package/dist/connectors/baseConnector.js.map +1 -1
- package/dist/connectors/caldiy.d.ts +137 -0
- package/dist/connectors/caldiy.js +424 -0
- package/dist/connectors/caldiy.js.map +1 -0
- package/dist/connectors/circleci.d.ts +162 -0
- package/dist/connectors/circleci.js +576 -0
- package/dist/connectors/circleci.js.map +1 -0
- package/dist/connectors/cloudflare.d.ts +132 -0
- package/dist/connectors/cloudflare.js +622 -0
- package/dist/connectors/cloudflare.js.map +1 -0
- package/dist/connectors/confluence.js +35 -0
- package/dist/connectors/confluence.js.map +1 -1
- package/dist/connectors/connectorRedirectUri.d.ts +30 -0
- package/dist/connectors/connectorRedirectUri.js +38 -0
- package/dist/connectors/connectorRedirectUri.js.map +1 -0
- package/dist/connectors/connectorRegistry.d.ts +60 -0
- package/dist/connectors/connectorRegistry.js +354 -0
- package/dist/connectors/connectorRegistry.js.map +1 -0
- package/dist/connectors/datadog.js +41 -5
- package/dist/connectors/datadog.js.map +1 -1
- package/dist/connectors/discord.js +14 -10
- package/dist/connectors/discord.js.map +1 -1
- package/dist/connectors/elasticsearch.d.ts +141 -0
- package/dist/connectors/elasticsearch.js +601 -0
- package/dist/connectors/elasticsearch.js.map +1 -0
- package/dist/connectors/figma.d.ts +130 -0
- package/dist/connectors/figma.js +387 -0
- package/dist/connectors/figma.js.map +1 -0
- package/dist/connectors/github.d.ts +17 -0
- package/dist/connectors/github.js +53 -2
- package/dist/connectors/github.js.map +1 -1
- package/dist/connectors/gitlab.js +12 -6
- package/dist/connectors/gitlab.js.map +1 -1
- package/dist/connectors/gmail.js +72 -21
- package/dist/connectors/gmail.js.map +1 -1
- package/dist/connectors/googleCalendar.js +8 -8
- package/dist/connectors/googleCalendar.js.map +1 -1
- package/dist/connectors/googleDocs.d.ts +103 -0
- package/dist/connectors/googleDocs.js +409 -0
- package/dist/connectors/googleDocs.js.map +1 -0
- package/dist/connectors/googleDrive.d.ts +12 -0
- package/dist/connectors/googleDrive.js +35 -8
- package/dist/connectors/googleDrive.js.map +1 -1
- package/dist/connectors/grafana.d.ts +133 -0
- package/dist/connectors/grafana.js +478 -0
- package/dist/connectors/grafana.js.map +1 -0
- package/dist/connectors/jira.d.ts +25 -0
- package/dist/connectors/jira.js +234 -3
- package/dist/connectors/jira.js.map +1 -1
- package/dist/connectors/linear.js +1 -1
- package/dist/connectors/linear.js.map +1 -1
- package/dist/connectors/mcpClient.js +37 -23
- package/dist/connectors/mcpClient.js.map +1 -1
- package/dist/connectors/mcpOAuth.js +78 -16
- package/dist/connectors/mcpOAuth.js.map +1 -1
- package/dist/connectors/monday.d.ts +217 -0
- package/dist/connectors/monday.js +655 -0
- package/dist/connectors/monday.js.map +1 -0
- package/dist/connectors/mongodb.d.ts +139 -0
- package/dist/connectors/mongodb.js +455 -0
- package/dist/connectors/mongodb.js.map +1 -0
- package/dist/connectors/notion.d.ts +1 -1
- package/dist/connectors/notion.js +13 -4
- package/dist/connectors/notion.js.map +1 -1
- package/dist/connectors/oauthStateStore.d.ts +20 -0
- package/dist/connectors/oauthStateStore.js +94 -4
- package/dist/connectors/oauthStateStore.js.map +1 -1
- package/dist/connectors/obsidian.d.ts +83 -0
- package/dist/connectors/obsidian.js +441 -0
- package/dist/connectors/obsidian.js.map +1 -0
- package/dist/connectors/paystack.d.ts +159 -0
- package/dist/connectors/paystack.js +607 -0
- package/dist/connectors/paystack.js.map +1 -0
- package/dist/connectors/pipedrive.d.ts +189 -0
- package/dist/connectors/pipedrive.js +559 -0
- package/dist/connectors/pipedrive.js.map +1 -0
- package/dist/connectors/postgres.d.ts +127 -0
- package/dist/connectors/postgres.js +512 -0
- package/dist/connectors/postgres.js.map +1 -0
- package/dist/connectors/posthog.d.ts +119 -0
- package/dist/connectors/posthog.js +479 -0
- package/dist/connectors/posthog.js.map +1 -0
- package/dist/connectors/redis.d.ts +140 -0
- package/dist/connectors/redis.js +579 -0
- package/dist/connectors/redis.js.map +1 -0
- package/dist/connectors/resend.d.ts +131 -0
- package/dist/connectors/resend.js +529 -0
- package/dist/connectors/resend.js.map +1 -0
- package/dist/connectors/salesforce.d.ts +136 -0
- package/dist/connectors/salesforce.js +603 -0
- package/dist/connectors/salesforce.js.map +1 -0
- package/dist/connectors/secrets.d.ts +51 -0
- package/dist/connectors/secrets.js +102 -0
- package/dist/connectors/secrets.js.map +1 -0
- package/dist/connectors/sendgrid.d.ts +102 -0
- package/dist/connectors/sendgrid.js +423 -0
- package/dist/connectors/sendgrid.js.map +1 -0
- package/dist/connectors/shopify.d.ts +145 -0
- package/dist/connectors/shopify.js +502 -0
- package/dist/connectors/shopify.js.map +1 -0
- package/dist/connectors/slack.d.ts +1 -1
- package/dist/connectors/slack.js +61 -9
- package/dist/connectors/slack.js.map +1 -1
- package/dist/connectors/snowflake.d.ts +119 -0
- package/dist/connectors/snowflake.js +633 -0
- package/dist/connectors/snowflake.js.map +1 -0
- package/dist/connectors/supabase.d.ts +92 -0
- package/dist/connectors/supabase.js +630 -0
- package/dist/connectors/supabase.js.map +1 -0
- package/dist/connectors/todoist.d.ts +117 -0
- package/dist/connectors/todoist.js +485 -0
- package/dist/connectors/todoist.js.map +1 -0
- package/dist/connectors/tokenStorage.js +56 -14
- package/dist/connectors/tokenStorage.js.map +1 -1
- package/dist/connectors/twilio.d.ts +118 -0
- package/dist/connectors/twilio.js +475 -0
- package/dist/connectors/twilio.js.map +1 -0
- package/dist/connectors/vercel.d.ts +110 -0
- package/dist/connectors/vercel.js +479 -0
- package/dist/connectors/vercel.js.map +1 -0
- package/dist/connectors/webflow.d.ts +118 -0
- package/dist/connectors/webflow.js +393 -0
- package/dist/connectors/webflow.js.map +1 -0
- package/dist/connectors/woocommerce.d.ts +220 -0
- package/dist/connectors/woocommerce.js +464 -0
- package/dist/connectors/woocommerce.js.map +1 -0
- package/dist/crypto.js +7 -2
- package/dist/crypto.js.map +1 -1
- package/dist/decisionReplay.d.ts +8 -6
- package/dist/decisionReplay.js +50 -17
- package/dist/decisionReplay.js.map +1 -1
- package/dist/decisionTraceLog.d.ts +28 -0
- package/dist/decisionTraceLog.js +156 -29
- package/dist/decisionTraceLog.js.map +1 -1
- package/dist/drivers/claude/api.js +33 -9
- package/dist/drivers/claude/api.js.map +1 -1
- package/dist/drivers/claude/envSanitizer.d.ts +0 -6
- package/dist/drivers/claude/envSanitizer.js +17 -2
- package/dist/drivers/claude/envSanitizer.js.map +1 -1
- package/dist/drivers/claude/streamParser.js +5 -4
- package/dist/drivers/claude/streamParser.js.map +1 -1
- package/dist/drivers/claude/subprocess.js +69 -16
- package/dist/drivers/claude/subprocess.js.map +1 -1
- package/dist/drivers/gemini/index.d.ts +22 -0
- package/dist/drivers/gemini/index.js +256 -129
- package/dist/drivers/gemini/index.js.map +1 -1
- package/dist/drivers/local/index.d.ts +2 -0
- package/dist/drivers/local/index.js +12 -0
- package/dist/drivers/local/index.js.map +1 -1
- package/dist/drivers/openai/index.js +30 -2
- package/dist/drivers/openai/index.js.map +1 -1
- package/dist/extensionClient.d.ts +37 -4
- package/dist/extensionClient.js +58 -16
- package/dist/extensionClient.js.map +1 -1
- package/dist/featureFlags.d.ts +91 -0
- package/dist/featureFlags.js +174 -3
- package/dist/featureFlags.js.map +1 -1
- package/dist/fileLock.js +21 -12
- package/dist/fileLock.js.map +1 -1
- package/dist/fileLockSync.d.ts +67 -0
- package/dist/fileLockSync.js +126 -0
- package/dist/fileLockSync.js.map +1 -0
- package/dist/fp/activityAnalytics.js +26 -12
- package/dist/fp/activityAnalytics.js.map +1 -1
- package/dist/fp/automationInterpreter.d.ts +15 -1
- package/dist/fp/automationInterpreter.js +267 -87
- package/dist/fp/automationInterpreter.js.map +1 -1
- package/dist/fp/automationProgram.d.ts +30 -0
- package/dist/fp/automationProgram.js.map +1 -1
- package/dist/fp/automationState.d.ts +24 -5
- package/dist/fp/automationState.js +56 -9
- package/dist/fp/automationState.js.map +1 -1
- package/dist/fp/automationUtils.js +1 -1
- package/dist/fp/automationUtils.js.map +1 -1
- package/dist/fp/commandDescription.js +7 -1
- package/dist/fp/commandDescription.js.map +1 -1
- package/dist/fp/extensionSnapshot.js +8 -2
- package/dist/fp/extensionSnapshot.js.map +1 -1
- package/dist/fp/interpreterContext.d.ts +66 -1
- package/dist/fp/interpreterContext.js +91 -1
- package/dist/fp/interpreterContext.js.map +1 -1
- package/dist/fp/policyParser.js +34 -2
- package/dist/fp/policyParser.js.map +1 -1
- package/dist/fsWatchWithFallback.d.ts +36 -0
- package/dist/fsWatchWithFallback.js +128 -0
- package/dist/fsWatchWithFallback.js.map +1 -0
- package/dist/haltPushDispatch.d.ts +33 -0
- package/dist/haltPushDispatch.js +103 -0
- package/dist/haltPushDispatch.js.map +1 -0
- package/dist/httpBodyValidation.d.ts +41 -0
- package/dist/httpBodyValidation.js +45 -0
- package/dist/httpBodyValidation.js.map +1 -0
- package/dist/inboxRoutes.d.ts +22 -0
- package/dist/inboxRoutes.js +61 -1
- package/dist/inboxRoutes.js.map +1 -1
- package/dist/index.js +1206 -158
- package/dist/index.js.map +1 -1
- package/dist/installGuard.js +6 -2
- package/dist/installGuard.js.map +1 -1
- package/dist/localEndpointGuard.d.ts +25 -0
- package/dist/localEndpointGuard.js +101 -0
- package/dist/localEndpointGuard.js.map +1 -0
- package/dist/lockfile.js +31 -4
- package/dist/lockfile.js.map +1 -1
- package/dist/oauth.d.ts +9 -0
- package/dist/oauth.js +33 -0
- package/dist/oauth.js.map +1 -1
- package/dist/oauthRoutes.d.ts +1 -1
- package/dist/oauthRoutes.js +5 -3
- package/dist/oauthRoutes.js.map +1 -1
- package/dist/orchestrator/childBridgeClient.d.ts +5 -0
- package/dist/orchestrator/childBridgeClient.js +28 -1
- package/dist/orchestrator/childBridgeClient.js.map +1 -1
- package/dist/orchestrator/childBridgeRegistry.js +29 -11
- package/dist/orchestrator/childBridgeRegistry.js.map +1 -1
- package/dist/orchestrator/orchestratorBridge.d.ts +33 -0
- package/dist/orchestrator/orchestratorBridge.js +70 -9
- package/dist/orchestrator/orchestratorBridge.js.map +1 -1
- package/dist/patchworkConfig.d.ts +44 -1
- package/dist/patchworkConfig.js +28 -3
- package/dist/patchworkConfig.js.map +1 -1
- package/dist/pluginLoader.js +10 -1
- package/dist/pluginLoader.js.map +1 -1
- package/dist/pluginWatcher.js +12 -14
- package/dist/pluginWatcher.js.map +1 -1
- package/dist/preToolUseHook.js +3 -2
- package/dist/preToolUseHook.js.map +1 -1
- package/dist/processTree.d.ts +34 -0
- package/dist/processTree.js +105 -0
- package/dist/processTree.js.map +1 -0
- package/dist/prompts.js +3 -3
- package/dist/prompts.js.map +1 -1
- package/dist/recipeOrchestration.d.ts +9 -0
- package/dist/recipeOrchestration.js +341 -24
- package/dist/recipeOrchestration.js.map +1 -1
- package/dist/recipeRoutes.d.ts +58 -2
- package/dist/recipeRoutes.js +744 -115
- package/dist/recipeRoutes.js.map +1 -1
- package/dist/recipes/RecipeOrchestrator.d.ts +2 -0
- package/dist/recipes/RecipeOrchestrator.js +6 -1
- package/dist/recipes/RecipeOrchestrator.js.map +1 -1
- package/dist/recipes/agentExecutor.d.ts +25 -5
- package/dist/recipes/agentExecutor.js.map +1 -1
- package/dist/recipes/chainedRunner.d.ts +2 -0
- package/dist/recipes/chainedRunner.js +156 -9
- package/dist/recipes/chainedRunner.js.map +1 -1
- package/dist/recipes/compiler.js +9 -5
- package/dist/recipes/compiler.js.map +1 -1
- package/dist/recipes/connectorPreflight.d.ts +66 -0
- package/dist/recipes/connectorPreflight.js +169 -0
- package/dist/recipes/connectorPreflight.js.map +1 -0
- package/dist/recipes/dependencyGraph.d.ts +9 -0
- package/dist/recipes/dependencyGraph.js +32 -7
- package/dist/recipes/dependencyGraph.js.map +1 -1
- package/dist/recipes/githubInstallSource.d.ts +135 -0
- package/dist/recipes/githubInstallSource.js +236 -0
- package/dist/recipes/githubInstallSource.js.map +1 -0
- package/dist/recipes/haltCategory.d.ts +119 -0
- package/dist/recipes/haltCategory.js +188 -0
- package/dist/recipes/haltCategory.js.map +1 -0
- package/dist/recipes/idempotencyKey.d.ts +154 -0
- package/dist/recipes/idempotencyKey.js +375 -0
- package/dist/recipes/idempotencyKey.js.map +1 -0
- package/dist/recipes/installer.js +58 -3
- package/dist/recipes/installer.js.map +1 -1
- package/dist/recipes/judgeSummary.d.ts +50 -0
- package/dist/recipes/judgeSummary.js +47 -0
- package/dist/recipes/judgeSummary.js.map +1 -0
- package/dist/recipes/judgeVerdict.d.ts +48 -0
- package/dist/recipes/judgeVerdict.js +174 -0
- package/dist/recipes/judgeVerdict.js.map +1 -0
- package/dist/recipes/migrations/index.d.ts +9 -0
- package/dist/recipes/migrations/index.js +133 -0
- package/dist/recipes/migrations/index.js.map +1 -1
- package/dist/recipes/names.d.ts +20 -0
- package/dist/recipes/names.js +25 -0
- package/dist/recipes/names.js.map +1 -1
- package/dist/recipes/parser.js +116 -6
- package/dist/recipes/parser.js.map +1 -1
- package/dist/recipes/replayRun.js +1 -1
- package/dist/recipes/replayRun.js.map +1 -1
- package/dist/recipes/runBudget.d.ts +70 -0
- package/dist/recipes/runBudget.js +109 -0
- package/dist/recipes/runBudget.js.map +1 -0
- package/dist/recipes/scheduler.d.ts +30 -0
- package/dist/recipes/scheduler.js +69 -19
- package/dist/recipes/scheduler.js.map +1 -1
- package/dist/recipes/schema.d.ts +65 -2
- package/dist/recipes/schemaGenerator.js +119 -7
- package/dist/recipes/schemaGenerator.js.map +1 -1
- package/dist/recipes/stepObservation.js +9 -0
- package/dist/recipes/stepObservation.js.map +1 -1
- package/dist/recipes/toolRegistry.js +33 -7
- package/dist/recipes/toolRegistry.js.map +1 -1
- package/dist/recipes/tools/airtable.d.ts +15 -0
- package/dist/recipes/tools/airtable.js +239 -0
- package/dist/recipes/tools/airtable.js.map +1 -0
- package/dist/recipes/tools/fanOut.d.ts +20 -0
- package/dist/recipes/tools/fanOut.js +199 -0
- package/dist/recipes/tools/fanOut.js.map +1 -0
- package/dist/recipes/tools/file.js +5 -2
- package/dist/recipes/tools/file.js.map +1 -1
- package/dist/recipes/tools/github.d.ts +1 -1
- package/dist/recipes/tools/github.js +75 -1
- package/dist/recipes/tools/github.js.map +1 -1
- package/dist/recipes/tools/gmail.js +27 -5
- package/dist/recipes/tools/gmail.js.map +1 -1
- package/dist/recipes/tools/googleDrive.js +64 -0
- package/dist/recipes/tools/googleDrive.js.map +1 -1
- package/dist/recipes/tools/http.d.ts +10 -0
- package/dist/recipes/tools/http.js +176 -0
- package/dist/recipes/tools/http.js.map +1 -0
- package/dist/recipes/tools/index.d.ts +7 -0
- package/dist/recipes/tools/index.js +7 -0
- package/dist/recipes/tools/index.js.map +1 -1
- package/dist/recipes/tools/resend.d.ts +8 -0
- package/dist/recipes/tools/resend.js +152 -0
- package/dist/recipes/tools/resend.js.map +1 -0
- package/dist/recipes/tools/sendgrid.d.ts +9 -0
- package/dist/recipes/tools/sendgrid.js +174 -0
- package/dist/recipes/tools/sendgrid.js.map +1 -0
- package/dist/recipes/tools/slack.js +1 -1
- package/dist/recipes/tools/twilio.d.ts +11 -0
- package/dist/recipes/tools/twilio.js +179 -0
- package/dist/recipes/tools/twilio.js.map +1 -0
- package/dist/recipes/tools/vercel.d.ts +9 -0
- package/dist/recipes/tools/vercel.js +145 -0
- package/dist/recipes/tools/vercel.js.map +1 -0
- package/dist/recipes/validation.d.ts +17 -0
- package/dist/recipes/validation.js +141 -11
- package/dist/recipes/validation.js.map +1 -1
- package/dist/recipes/workspaceRoot.d.ts +37 -0
- package/dist/recipes/workspaceRoot.js +73 -0
- package/dist/recipes/workspaceRoot.js.map +1 -0
- package/dist/recipes/yamlPositions.d.ts +56 -0
- package/dist/recipes/yamlPositions.js +183 -0
- package/dist/recipes/yamlPositions.js.map +1 -0
- package/dist/recipes/yamlRunner.d.ts +185 -8
- package/dist/recipes/yamlRunner.js +967 -224
- package/dist/recipes/yamlRunner.js.map +1 -1
- package/dist/recipesHttp.d.ts +19 -3
- package/dist/recipesHttp.js +67 -11
- package/dist/recipesHttp.js.map +1 -1
- package/dist/resources.js +21 -13
- package/dist/resources.js.map +1 -1
- package/dist/runLog.d.ts +58 -5
- package/dist/runLog.js +98 -21
- package/dist/runLog.js.map +1 -1
- package/dist/sanitizeParsedJson.d.ts +39 -0
- package/dist/sanitizeParsedJson.js +55 -0
- package/dist/sanitizeParsedJson.js.map +1 -0
- package/dist/schemas/recipe.v1.json +887 -198
- package/dist/server.d.ts +160 -3
- package/dist/server.js +1099 -122
- package/dist/server.js.map +1 -1
- package/dist/sessionCheckpoint.d.ts +8 -0
- package/dist/sessionCheckpoint.js +33 -3
- package/dist/sessionCheckpoint.js.map +1 -1
- package/dist/ssrfGuard.d.ts +16 -0
- package/dist/ssrfGuard.js +87 -4
- package/dist/ssrfGuard.js.map +1 -1
- package/dist/streamableHttp.d.ts +9 -4
- package/dist/streamableHttp.js +76 -20
- package/dist/streamableHttp.js.map +1 -1
- package/dist/telemetry.js +37 -19
- package/dist/telemetry.js.map +1 -1
- package/dist/testing/shadowRun.d.ts +52 -0
- package/dist/testing/shadowRun.js +71 -0
- package/dist/testing/shadowRun.js.map +1 -0
- package/dist/tools/batchLsp.d.ts +3 -0
- package/dist/tools/bridgeDoctor.d.ts +11 -0
- package/dist/tools/bridgeDoctor.js +48 -2
- package/dist/tools/bridgeDoctor.js.map +1 -1
- package/dist/tools/bridgeStatus.d.ts +0 -12
- package/dist/tools/bridgeStatus.js +2 -28
- package/dist/tools/bridgeStatus.js.map +1 -1
- package/dist/tools/cancelClaudeTask.d.ts +1 -0
- package/dist/tools/clipboard.d.ts +2 -0
- package/dist/tools/closeTabs.d.ts +1 -0
- package/dist/tools/codeLens.d.ts +1 -0
- package/dist/tools/createIssueFromAIComment.d.ts +1 -0
- package/dist/tools/ctxGetTaskContext.d.ts +9 -0
- package/dist/tools/ctxGetTaskContext.js +50 -2
- package/dist/tools/ctxGetTaskContext.js.map +1 -1
- package/dist/tools/ctxQueryTraces.js +32 -24
- package/dist/tools/ctxQueryTraces.js.map +1 -1
- package/dist/tools/ctxSaveTrace.d.ts +1 -0
- package/dist/tools/debug.d.ts +4 -0
- package/dist/tools/decorations.d.ts +2 -0
- package/dist/tools/detectUnusedCode.js +9 -7
- package/dist/tools/detectUnusedCode.js.map +1 -1
- package/dist/tools/documentLinks.d.ts +1 -0
- package/dist/tools/editText.d.ts +1 -0
- package/dist/tools/editText.js +2 -1
- package/dist/tools/editText.js.map +1 -1
- package/dist/tools/enrichCommit.d.ts +1 -0
- package/dist/tools/enrichStackTrace.js +11 -5
- package/dist/tools/enrichStackTrace.js.map +1 -1
- package/dist/tools/explainDiagnostic.d.ts +1 -0
- package/dist/tools/explainSymbol.d.ts +1 -0
- package/dist/tools/fileOperations.d.ts +3 -0
- package/dist/tools/fileOperations.js +2 -1
- package/dist/tools/fileOperations.js.map +1 -1
- package/dist/tools/fileWatcher.d.ts +2 -0
- package/dist/tools/fileWatcher.js +8 -2
- package/dist/tools/fileWatcher.js.map +1 -1
- package/dist/tools/findFiles.d.ts +1 -0
- package/dist/tools/fixAllLintErrors.d.ts +1 -0
- package/dist/tools/fixAllLintErrors.js +10 -5
- package/dist/tools/fixAllLintErrors.js.map +1 -1
- package/dist/tools/foldingRanges.d.ts +1 -0
- package/dist/tools/formatDocument.d.ts +1 -0
- package/dist/tools/formatDocument.js +10 -5
- package/dist/tools/formatDocument.js.map +1 -1
- package/dist/tools/generateTests.d.ts +1 -0
- package/dist/tools/getAIComments.d.ts +1 -0
- package/dist/tools/getAnalyticsReport.js +5 -1
- package/dist/tools/getAnalyticsReport.js.map +1 -1
- package/dist/tools/getBufferContent.d.ts +1 -0
- package/dist/tools/getChangeImpact.d.ts +1 -0
- package/dist/tools/getChangeImpact.js +23 -14
- package/dist/tools/getChangeImpact.js.map +1 -1
- package/dist/tools/getClaudeTaskStatus.d.ts +1 -0
- package/dist/tools/getCodeCoverage.d.ts +1 -0
- package/dist/tools/getCodeCoverage.js +32 -14
- package/dist/tools/getCodeCoverage.js.map +1 -1
- package/dist/tools/getCommitsForIssue.d.ts +1 -0
- package/dist/tools/getDebugState.d.ts +1 -0
- package/dist/tools/getDiagnostics.js +32 -29
- package/dist/tools/getDiagnostics.js.map +1 -1
- package/dist/tools/getDiffFromHandoff.js +8 -2
- package/dist/tools/getDiffFromHandoff.js.map +1 -1
- package/dist/tools/getDocumentSymbols.d.ts +1 -0
- package/dist/tools/getGitHotspots.d.ts +1 -0
- package/dist/tools/getGitStatus.js +6 -1
- package/dist/tools/getGitStatus.js.map +1 -1
- package/dist/tools/getImportedSignatures.d.ts +1 -0
- package/dist/tools/getImportedSignatures.js +18 -14
- package/dist/tools/getImportedSignatures.js.map +1 -1
- package/dist/tools/getPRTemplate.d.ts +1 -0
- package/dist/tools/getProjectContext.js +23 -10
- package/dist/tools/getProjectContext.js.map +1 -1
- package/dist/tools/getSymbolHistory.d.ts +1 -0
- package/dist/tools/getToolCapabilities.d.ts +10 -5
- package/dist/tools/getToolCapabilities.js +116 -204
- package/dist/tools/getToolCapabilities.js.map +1 -1
- package/dist/tools/getTypeSignature.d.ts +1 -0
- package/dist/tools/getWorkspaceSettings.d.ts +1 -0
- package/dist/tools/gitWrite.d.ts +11 -0
- package/dist/tools/github/actions.d.ts +2 -0
- package/dist/tools/github/composite.d.ts +4 -1
- package/dist/tools/github/composite.js +7 -7
- package/dist/tools/github/composite.js.map +1 -1
- package/dist/tools/github/issues.d.ts +4 -0
- package/dist/tools/github/pr.d.ts +13 -6
- package/dist/tools/github/pr.js +18 -11
- package/dist/tools/github/pr.js.map +1 -1
- package/dist/tools/handoffNote.d.ts +1 -0
- package/dist/tools/handoffNote.js +2 -1
- package/dist/tools/handoffNote.js.map +1 -1
- package/dist/tools/headless/lspClient.js +3 -0
- package/dist/tools/headless/lspClient.js.map +1 -1
- package/dist/tools/hoverAtCursor.d.ts +1 -0
- package/dist/tools/httpClient.d.ts +2 -0
- package/dist/tools/httpClient.js +38 -71
- package/dist/tools/httpClient.js.map +1 -1
- package/dist/tools/index.js +14 -10
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/inlayHints.d.ts +1 -0
- package/dist/tools/launchQuickTask.d.ts +1 -0
- package/dist/tools/listClaudeTasks.d.ts +1 -0
- package/dist/tools/listClaudeTasks.js +10 -8
- package/dist/tools/listClaudeTasks.js.map +1 -1
- package/dist/tools/listTerminals.d.ts +1 -0
- package/dist/tools/lsp.d.ts +15 -0
- package/dist/tools/lsp.js +17 -0
- package/dist/tools/lsp.js.map +1 -1
- package/dist/tools/navigateToSymbolByName.d.ts +1 -0
- package/dist/tools/openDiff.d.ts +1 -0
- package/dist/tools/openDiff.js +4 -1
- package/dist/tools/openDiff.js.map +1 -1
- package/dist/tools/openFile.d.ts +1 -0
- package/dist/tools/openFile.js +4 -1
- package/dist/tools/openFile.js.map +1 -1
- package/dist/tools/openInBrowser.js +6 -1
- package/dist/tools/openInBrowser.js.map +1 -1
- package/dist/tools/organizeImports.d.ts +1 -0
- package/dist/tools/organizeImports.js +5 -3
- package/dist/tools/organizeImports.js.map +1 -1
- package/dist/tools/performanceReport.js +5 -3
- package/dist/tools/performanceReport.js.map +1 -1
- package/dist/tools/planPersistence.d.ts +3 -0
- package/dist/tools/planPersistence.js +4 -1
- package/dist/tools/planPersistence.js.map +1 -1
- package/dist/tools/previewEdit.d.ts +1 -0
- package/dist/tools/previewEdit.js +31 -5
- package/dist/tools/previewEdit.js.map +1 -1
- package/dist/tools/recentTracesDigest.js +54 -11
- package/dist/tools/recentTracesDigest.js.map +1 -1
- package/dist/tools/refactorAnalyze.d.ts +1 -0
- package/dist/tools/refactorExtractFunction.js +4 -1
- package/dist/tools/refactorExtractFunction.js.map +1 -1
- package/dist/tools/refactorPreview.d.ts +1 -0
- package/dist/tools/refactorPreview.js +10 -2
- package/dist/tools/refactorPreview.js.map +1 -1
- package/dist/tools/replaceBlock.d.ts +1 -0
- package/dist/tools/replaceBlock.js +2 -1
- package/dist/tools/replaceBlock.js.map +1 -1
- package/dist/tools/resumeClaudeTask.d.ts +1 -0
- package/dist/tools/runClaudeTask.d.ts +1 -0
- package/dist/tools/runTests.js +15 -4
- package/dist/tools/runTests.js.map +1 -1
- package/dist/tools/screenshot.d.ts +1 -0
- package/dist/tools/screenshotAndAnnotate.js +6 -2
- package/dist/tools/screenshotAndAnnotate.js.map +1 -1
- package/dist/tools/searchAndReplace.d.ts +1 -0
- package/dist/tools/searchAndReplace.js +21 -12
- package/dist/tools/searchAndReplace.js.map +1 -1
- package/dist/tools/searchTools.d.ts +26 -0
- package/dist/tools/searchTools.js +62 -21
- package/dist/tools/searchTools.js.map +1 -1
- package/dist/tools/searchWorkspace.d.ts +1 -0
- package/dist/tools/selectionRanges.d.ts +1 -0
- package/dist/tools/semanticTokens.d.ts +1 -0
- package/dist/tools/signatureHelp.d.ts +1 -0
- package/dist/tools/spawnWorkspace.js +15 -7
- package/dist/tools/spawnWorkspace.js.map +1 -1
- package/dist/tools/terminal.d.ts +6 -0
- package/dist/tools/testRunners/pytest.js +6 -2
- package/dist/tools/testRunners/pytest.js.map +1 -1
- package/dist/tools/testRunners/vitestJest.js +3 -1
- package/dist/tools/testRunners/vitestJest.js.map +1 -1
- package/dist/tools/testTraceToSource.d.ts +1 -0
- package/dist/tools/transaction.d.ts +13 -1
- package/dist/tools/transaction.js +63 -5
- package/dist/tools/transaction.js.map +1 -1
- package/dist/tools/typeHierarchy.d.ts +1 -0
- package/dist/tools/utils.d.ts +33 -0
- package/dist/tools/utils.js +122 -18
- package/dist/tools/utils.js.map +1 -1
- package/dist/tools/vscodeCommands.d.ts +2 -0
- package/dist/tools/vscodeTasks.d.ts +2 -0
- package/dist/tools/workspaceSettings.d.ts +1 -0
- package/dist/transport.d.ts +3 -1
- package/dist/transport.js +126 -52
- package/dist/transport.js.map +1 -1
- package/dist/winShim.d.ts +34 -0
- package/dist/winShim.js +94 -0
- package/dist/winShim.js.map +1 -0
- package/dist/wireHaltPushDispatch.d.ts +38 -0
- package/dist/wireHaltPushDispatch.js +71 -0
- package/dist/wireHaltPushDispatch.js.map +1 -0
- package/dist/writeFileAtomic.d.ts +23 -0
- package/dist/writeFileAtomic.js +121 -0
- package/dist/writeFileAtomic.js.map +1 -0
- package/package.json +23 -7
- package/scripts/postinstall.mjs +42 -2
- package/scripts/smoke/run-all.mjs +213 -0
- package/scripts/start-all.mjs +572 -0
- package/scripts/start-all.ps1 +209 -0
- package/scripts/start-all.sh +73 -17
- package/scripts/start-orchestrator.ps1 +158 -0
- package/scripts/start-remote.mjs +122 -0
- package/templates/automation-policies/recipe-authoring.json +1 -1
- package/templates/automation-policies/security-first.json +1 -1
- package/templates/automation-policies/strict-lint.json +1 -1
- package/templates/automation-policies/test-driven.json +1 -1
- package/templates/automation-policy.example.json +1 -1
- package/templates/co.patchwork-os.bridge.plist +2 -2
- package/templates/recipes/approval-queue-ui-test.yaml +1 -1
- package/templates/recipes/ctx-loop-test.yaml +1 -1
- package/templates/recipes/fix-errors-on-save.yaml +71 -0
- package/templates/recipes/morning-brief.yaml +5 -2
- package/templates/recipes/project-health-check.yaml +4 -1
- package/templates/recipes/sentry-to-linear.yaml +72 -38
- package/templates/recipes/webhook/apple-watch-health-log.yaml +145 -0
- package/templates/recipes/webhook/customer-escalation.yaml +8 -9
- package/templates/recipes/webhook/meeting-prep.yaml +11 -5
- package/dist/commands/marketplace.d.ts +0 -16
- package/dist/commands/marketplace.js +0 -32
- package/dist/commands/marketplace.js.map +0 -1
- package/dist/recipes/legacyRecipeCompat.d.ts +0 -10
- package/dist/recipes/legacyRecipeCompat.js +0 -131
- package/dist/recipes/legacyRecipeCompat.js.map +0 -1
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Idempotency keys for write-tool calls.
|
|
3
|
+
*
|
|
4
|
+
* PR5a of the Val-inspired plan. Foundation for safe retry + safe resume.
|
|
5
|
+
*
|
|
6
|
+
* Two pieces:
|
|
7
|
+
*
|
|
8
|
+
* `deriveIdempotencyKey(toolId, params)`
|
|
9
|
+
* A stable, deterministic hash over `(toolId, canonicalised params)`.
|
|
10
|
+
* Canonicalisation = JSON.stringify with sorted keys, recursive — so
|
|
11
|
+
* `{ a: 1, b: 2 }` and `{ b: 2, a: 1 }` hash identically. Returns a
|
|
12
|
+
* hex SHA-256 prefix (first 16 chars; collisions vanishingly small
|
|
13
|
+
* within a single run scope).
|
|
14
|
+
*
|
|
15
|
+
* `WriteEffectLedger`
|
|
16
|
+
* Per-run in-memory map of key → cached output. The runner constructs
|
|
17
|
+
* one per recipe run and threads it through `StepDeps` / `ToolContext`.
|
|
18
|
+
* `toolRegistry.executeTool` checks the ledger before invoking write
|
|
19
|
+
* tools; if the key is present, returns the cached output instead of
|
|
20
|
+
* re-executing — preventing duplicate side effects when two parallel
|
|
21
|
+
* branches of a chained recipe both call the same write tool with the
|
|
22
|
+
* same params.
|
|
23
|
+
*
|
|
24
|
+
* Scope of this PR (deliberately narrow):
|
|
25
|
+
* - In-run dedup only (Map lives for one recipe run, discarded after).
|
|
26
|
+
* - Records only on successful execution; failures don't pollute the
|
|
27
|
+
* ledger, so retry-after-failure still re-executes (correct: if the
|
|
28
|
+
* tool failed, we can't assume the side effect happened). A failure
|
|
29
|
+
* is EITHER a thrown/rejected error OR a resolved JSON `{ok:false}`
|
|
30
|
+
* envelope — connector write tools (e.g. asana.create_task) catch
|
|
31
|
+
* transient errors and RETURN `JSON.stringify({ok:false, error})`
|
|
32
|
+
* rather than throwing, and that return-value failure must also skip
|
|
33
|
+
* the cache (see `isReturnValueFailure` + `getOrExecute`).
|
|
34
|
+
* - No cross-run persistence — that's PR5b (disk-backed effect ledger).
|
|
35
|
+
* - No retry-time idempotency on partial-failure cases (Slack posted
|
|
36
|
+
* but HTTP timed out); that needs tool-side support and is a future
|
|
37
|
+
* PR.
|
|
38
|
+
*
|
|
39
|
+
* The protection this DOES provide today: a `parallel:` block (or a
|
|
40
|
+
* recipe that calls a write tool from two different chained steps with
|
|
41
|
+
* identical params) cannot duplicate the side effect. Concretely, this
|
|
42
|
+
* was a footgun that pre-dated PR5a: `chainedRunner.ts` schedules steps
|
|
43
|
+
* with dependency-graph parallelism; if two branches happen to call
|
|
44
|
+
* `slack.postMessage` with the same payload, the message went twice.
|
|
45
|
+
*/
|
|
46
|
+
import type { Logger } from "../logger.js";
|
|
47
|
+
/**
|
|
48
|
+
* Derive a stable idempotency key for a write-tool invocation. 16 hex
|
|
49
|
+
* chars is 64 bits of entropy — far more than enough for in-run dedup
|
|
50
|
+
* (a single recipe with even 10⁵ steps has ~5×10⁻¹⁰ collision risk).
|
|
51
|
+
*/
|
|
52
|
+
export declare function deriveIdempotencyKey(toolId: string, params: Record<string, unknown>): string;
|
|
53
|
+
/**
|
|
54
|
+
* Compose a collision-safe scope key from `(recipeName, manualRunId)`.
|
|
55
|
+
*
|
|
56
|
+
* Naive `${recipeName}:${manualRunId}` is ambiguous: recipe `a:b` +
|
|
57
|
+
* attempt `c` and recipe `a` + attempt `b:c` both produce `a:b:c` and
|
|
58
|
+
* would share a ledger scope, letting one attempt read another's
|
|
59
|
+
* cached write-tool outputs. We hash both fields separately as a JSON
|
|
60
|
+
* array so the encoding is unambiguous regardless of either field's
|
|
61
|
+
* contents.
|
|
62
|
+
*
|
|
63
|
+
* Returned as a 32-hex-char SHA-256 prefix — long enough that
|
|
64
|
+
* collisions across a realistic ledger are effectively impossible
|
|
65
|
+
* (~2^128 birthday bound), short enough to scan in a JSONL row.
|
|
66
|
+
*/
|
|
67
|
+
export declare function deriveScopeKey(recipeName: string, manualRunId: string): string;
|
|
68
|
+
export declare function assertValidManualRunId(id: string): string;
|
|
69
|
+
/**
|
|
70
|
+
* In-memory per-run ledger of executed write-tool calls. Maps idempotency
|
|
71
|
+
* keys to the cached output the tool returned, so a duplicate call can
|
|
72
|
+
* be short-circuited to the same result the first call produced.
|
|
73
|
+
*
|
|
74
|
+
* The ledger is single-threaded by design — runners are single-process
|
|
75
|
+
* and a per-run ledger has no cross-thread access. Concurrency safety
|
|
76
|
+
* within a run is provided by the dependency graph (parallel-only steps
|
|
77
|
+
* with no shared params hash by construction); the ledger catches
|
|
78
|
+
* accidental same-params calls.
|
|
79
|
+
*/
|
|
80
|
+
/**
|
|
81
|
+
* Optional disk-backed persistence for the ledger.
|
|
82
|
+
*
|
|
83
|
+
* PR5b — extends in-memory dedup so a *retry* of the same logical
|
|
84
|
+
* `(recipeName, manualRunId)` attempt won't replay side effects. The
|
|
85
|
+
* ledger stays per-attempt; cron/webhook runs and recipes without a
|
|
86
|
+
* manualRunId stay purely in memory (no scope key = nothing to write).
|
|
87
|
+
*
|
|
88
|
+
* File layout: a single JSONL at `${dir}/effect_ledger.jsonl`. Each row
|
|
89
|
+
* is `{scopeKey, idemKey, output, recordedAt}`. On construction, the
|
|
90
|
+
* ledger streams the file and rehydrates entries whose `scopeKey`
|
|
91
|
+
* matches the configured scope; everything else is left alone for the
|
|
92
|
+
* other attempts' ledgers to pick up.
|
|
93
|
+
*
|
|
94
|
+
* Failure mode: any IO error falls back to in-memory operation and logs
|
|
95
|
+
* a warning. A partially-replayed attempt with an unreadable ledger
|
|
96
|
+
* degrades to "re-execute side effects" — louder than "silently dedup
|
|
97
|
+
* something we can't audit".
|
|
98
|
+
*/
|
|
99
|
+
export interface DiskLedgerOptions {
|
|
100
|
+
/** Directory holding `effect_ledger.jsonl`. Created if missing. */
|
|
101
|
+
dir: string;
|
|
102
|
+
/** `${recipeName}:${manualRunId}` — composed by the caller. */
|
|
103
|
+
scopeKey: string;
|
|
104
|
+
logger?: Logger;
|
|
105
|
+
}
|
|
106
|
+
export declare class WriteEffectLedger {
|
|
107
|
+
private readonly cache;
|
|
108
|
+
private readonly inFlight;
|
|
109
|
+
private readonly disk;
|
|
110
|
+
private readonly file;
|
|
111
|
+
constructor(disk?: DiskLedgerOptions);
|
|
112
|
+
has(key: string): boolean;
|
|
113
|
+
/**
|
|
114
|
+
* Return the previously-cached output for `key`, or `undefined` if not
|
|
115
|
+
* recorded. `null` is a legitimate cached value (= the tool returned
|
|
116
|
+
* `null` originally), so callers must use `has()` to distinguish "not
|
|
117
|
+
* present" from "present and null".
|
|
118
|
+
*/
|
|
119
|
+
get(key: string): string | null | undefined;
|
|
120
|
+
record(key: string, output: string | null): void;
|
|
121
|
+
/**
|
|
122
|
+
* Atomically check cache + in-flight map, then execute `fn` at most once
|
|
123
|
+
* per key. Concurrent callers with the same key share a single Promise —
|
|
124
|
+
* fixing the TOCTOU window that existed between `has()` and `record()`.
|
|
125
|
+
*
|
|
126
|
+
* Failures are not cached so retry-after-failure still re-executes. A
|
|
127
|
+
* failure is EITHER a rejected promise OR a resolved result that is a
|
|
128
|
+
* JSON-encoded `{ok:false, ...}` envelope. The latter case matters:
|
|
129
|
+
* connector write tools (e.g. `asana.create_task`) catch transient
|
|
130
|
+
* errors internally and RETURN `JSON.stringify({ok:false, error})`
|
|
131
|
+
* rather than throwing. If we cached that, the next retry would replay
|
|
132
|
+
* the cached failure as a silent no-op instead of re-executing — which
|
|
133
|
+
* is the entire class of "write tools that report failure by return
|
|
134
|
+
* value". So we parse the resolved result and treat `ok === false` as a
|
|
135
|
+
* failure: drop the in-flight entry, return the result to THIS caller,
|
|
136
|
+
* but do NOT record it, so a subsequent retry runs `fn` again.
|
|
137
|
+
*
|
|
138
|
+
* Genuine successes still cache: non-JSON strings (`JSON.parse` throws
|
|
139
|
+
* SyntaxError), `null`, and any JSON value without `ok === false` (e.g.
|
|
140
|
+
* `{ok:true}`) are recorded as before.
|
|
141
|
+
*/
|
|
142
|
+
getOrExecute(key: string, fn: () => Promise<string | null>): Promise<string | null>;
|
|
143
|
+
/** Test-only inspection of the current key set. */
|
|
144
|
+
keys(): string[];
|
|
145
|
+
size(): number;
|
|
146
|
+
private loadExisting;
|
|
147
|
+
private append;
|
|
148
|
+
/**
|
|
149
|
+
* Trim `effect_ledger.jsonl` to the most recent MAX_PERSIST_LINES.
|
|
150
|
+
* Best-effort — failure logs and the next append proceeds against the
|
|
151
|
+
* un-rotated file. Same pattern as RecipeRunLog / DecisionTraceLog.
|
|
152
|
+
*/
|
|
153
|
+
private rotate;
|
|
154
|
+
}
|
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Idempotency keys for write-tool calls.
|
|
3
|
+
*
|
|
4
|
+
* PR5a of the Val-inspired plan. Foundation for safe retry + safe resume.
|
|
5
|
+
*
|
|
6
|
+
* Two pieces:
|
|
7
|
+
*
|
|
8
|
+
* `deriveIdempotencyKey(toolId, params)`
|
|
9
|
+
* A stable, deterministic hash over `(toolId, canonicalised params)`.
|
|
10
|
+
* Canonicalisation = JSON.stringify with sorted keys, recursive — so
|
|
11
|
+
* `{ a: 1, b: 2 }` and `{ b: 2, a: 1 }` hash identically. Returns a
|
|
12
|
+
* hex SHA-256 prefix (first 16 chars; collisions vanishingly small
|
|
13
|
+
* within a single run scope).
|
|
14
|
+
*
|
|
15
|
+
* `WriteEffectLedger`
|
|
16
|
+
* Per-run in-memory map of key → cached output. The runner constructs
|
|
17
|
+
* one per recipe run and threads it through `StepDeps` / `ToolContext`.
|
|
18
|
+
* `toolRegistry.executeTool` checks the ledger before invoking write
|
|
19
|
+
* tools; if the key is present, returns the cached output instead of
|
|
20
|
+
* re-executing — preventing duplicate side effects when two parallel
|
|
21
|
+
* branches of a chained recipe both call the same write tool with the
|
|
22
|
+
* same params.
|
|
23
|
+
*
|
|
24
|
+
* Scope of this PR (deliberately narrow):
|
|
25
|
+
* - In-run dedup only (Map lives for one recipe run, discarded after).
|
|
26
|
+
* - Records only on successful execution; failures don't pollute the
|
|
27
|
+
* ledger, so retry-after-failure still re-executes (correct: if the
|
|
28
|
+
* tool failed, we can't assume the side effect happened). A failure
|
|
29
|
+
* is EITHER a thrown/rejected error OR a resolved JSON `{ok:false}`
|
|
30
|
+
* envelope — connector write tools (e.g. asana.create_task) catch
|
|
31
|
+
* transient errors and RETURN `JSON.stringify({ok:false, error})`
|
|
32
|
+
* rather than throwing, and that return-value failure must also skip
|
|
33
|
+
* the cache (see `isReturnValueFailure` + `getOrExecute`).
|
|
34
|
+
* - No cross-run persistence — that's PR5b (disk-backed effect ledger).
|
|
35
|
+
* - No retry-time idempotency on partial-failure cases (Slack posted
|
|
36
|
+
* but HTTP timed out); that needs tool-side support and is a future
|
|
37
|
+
* PR.
|
|
38
|
+
*
|
|
39
|
+
* The protection this DOES provide today: a `parallel:` block (or a
|
|
40
|
+
* recipe that calls a write tool from two different chained steps with
|
|
41
|
+
* identical params) cannot duplicate the side effect. Concretely, this
|
|
42
|
+
* was a footgun that pre-dated PR5a: `chainedRunner.ts` schedules steps
|
|
43
|
+
* with dependency-graph parallelism; if two branches happen to call
|
|
44
|
+
* `slack.postMessage` with the same payload, the message went twice.
|
|
45
|
+
*/
|
|
46
|
+
import { createHash } from "node:crypto";
|
|
47
|
+
import { appendFileSync, lstatSync, mkdirSync, readFileSync, statSync, } from "node:fs";
|
|
48
|
+
import path from "node:path";
|
|
49
|
+
import { writeFileAtomicSync } from "../writeFileAtomic.js";
|
|
50
|
+
/**
|
|
51
|
+
* Stable canonical-JSON serialiser. Recursively sorts object keys so two
|
|
52
|
+
* params records with the same shape but different key order produce the
|
|
53
|
+
* same string. Plain objects only — falls back to `JSON.stringify` for
|
|
54
|
+
* arrays / primitives / null.
|
|
55
|
+
*/
|
|
56
|
+
function canonicalise(value) {
|
|
57
|
+
if (value === null || typeof value !== "object") {
|
|
58
|
+
return JSON.stringify(value);
|
|
59
|
+
}
|
|
60
|
+
if (Array.isArray(value)) {
|
|
61
|
+
return `[${value.map(canonicalise).join(",")}]`;
|
|
62
|
+
}
|
|
63
|
+
const entries = Object.entries(value).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
|
|
64
|
+
const body = entries
|
|
65
|
+
.map(([k, v]) => `${JSON.stringify(k)}:${canonicalise(v)}`)
|
|
66
|
+
.join(",");
|
|
67
|
+
return `{${body}}`;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Derive a stable idempotency key for a write-tool invocation. 16 hex
|
|
71
|
+
* chars is 64 bits of entropy — far more than enough for in-run dedup
|
|
72
|
+
* (a single recipe with even 10⁵ steps has ~5×10⁻¹⁰ collision risk).
|
|
73
|
+
*/
|
|
74
|
+
export function deriveIdempotencyKey(toolId, params) {
|
|
75
|
+
const payload = `${toolId}|${canonicalise(params)}`;
|
|
76
|
+
return createHash("sha256").update(payload).digest("hex").slice(0, 16);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Compose a collision-safe scope key from `(recipeName, manualRunId)`.
|
|
80
|
+
*
|
|
81
|
+
* Naive `${recipeName}:${manualRunId}` is ambiguous: recipe `a:b` +
|
|
82
|
+
* attempt `c` and recipe `a` + attempt `b:c` both produce `a:b:c` and
|
|
83
|
+
* would share a ledger scope, letting one attempt read another's
|
|
84
|
+
* cached write-tool outputs. We hash both fields separately as a JSON
|
|
85
|
+
* array so the encoding is unambiguous regardless of either field's
|
|
86
|
+
* contents.
|
|
87
|
+
*
|
|
88
|
+
* Returned as a 32-hex-char SHA-256 prefix — long enough that
|
|
89
|
+
* collisions across a realistic ledger are effectively impossible
|
|
90
|
+
* (~2^128 birthday bound), short enough to scan in a JSONL row.
|
|
91
|
+
*/
|
|
92
|
+
export function deriveScopeKey(recipeName, manualRunId) {
|
|
93
|
+
const payload = JSON.stringify([recipeName, manualRunId]);
|
|
94
|
+
return createHash("sha256").update(payload).digest("hex").slice(0, 32);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* `manualRunId` charset validation — caller-supplied id from the CLI
|
|
98
|
+
* (`--attempt`), HTTP routes, or SDK. Rejects null bytes, control
|
|
99
|
+
* characters, path-traversal slugs (`/`, `\`, `..`), and anything
|
|
100
|
+
* longer than 64 chars. Returns the id verbatim when valid; throws
|
|
101
|
+
* with a descriptive message otherwise.
|
|
102
|
+
*
|
|
103
|
+
* Why strict: this string is hashed into the disk-ledger scope key,
|
|
104
|
+
* appended to `runs.jsonl` rows (capped audit-row size depends on it),
|
|
105
|
+
* and rendered into dashboard pills + CLI output. A 10 MB id would
|
|
106
|
+
* inflate every row past `MAX_PERSIST_BYTES` and erase audit during
|
|
107
|
+
* rotation; control characters break line-delimited persistence.
|
|
108
|
+
*/
|
|
109
|
+
const MANUAL_RUN_ID_PATTERN = /^[A-Za-z0-9_.-]{1,64}$/;
|
|
110
|
+
export function assertValidManualRunId(id) {
|
|
111
|
+
if (typeof id !== "string" || !MANUAL_RUN_ID_PATTERN.test(id)) {
|
|
112
|
+
throw new Error(`manualRunId must match ${MANUAL_RUN_ID_PATTERN} (1-64 chars of [A-Za-z0-9_.-]); got: ${JSON.stringify(id).slice(0, 80)}`);
|
|
113
|
+
}
|
|
114
|
+
return id;
|
|
115
|
+
}
|
|
116
|
+
const LEDGER_FILENAME = "effect_ledger.jsonl";
|
|
117
|
+
const MAX_PERSIST_BYTES = 1024 * 1024; // 1 MB — same posture as runLog
|
|
118
|
+
const MAX_PERSIST_LINES = 10_000;
|
|
119
|
+
/**
|
|
120
|
+
* Validate a caller-supplied ledger directory before any filesystem IO.
|
|
121
|
+
*
|
|
122
|
+
* The dir argument flows from the CLI (`--ledger-dir`) and (if/when
|
|
123
|
+
* exposed) HTTP-runner inputs; we'd rather fail loudly than write JSONL
|
|
124
|
+
* to whatever path is handed in. Three checks:
|
|
125
|
+
*
|
|
126
|
+
* - **No null bytes** — would short-circuit C string handling in older
|
|
127
|
+
* libc paths and confuse logs / audit tooling.
|
|
128
|
+
* - **Absolute** — relative paths resolve against `process.cwd()`,
|
|
129
|
+
* which for recipe runs is the workspace; an `--ledger-dir foo`
|
|
130
|
+
* silently scattering ledger files under recipe sources is the
|
|
131
|
+
* wrong default. Caller can pass `path.resolve(...)` explicitly if
|
|
132
|
+
* they want relative resolution.
|
|
133
|
+
* - **Not a symlink** — if the directory already exists and is a
|
|
134
|
+
* symlink, an attacker who can write to the symlink's owning dir
|
|
135
|
+
* can swap the target and redirect appends. Rejecting up front
|
|
136
|
+
* means a fresh dir is created (via mkdirSync) or an existing real
|
|
137
|
+
* dir is used; symlink-replacement on the JSONL file itself is
|
|
138
|
+
* handled separately on each read in `loadExisting`.
|
|
139
|
+
*/
|
|
140
|
+
function assertSafeLedgerDir(dir) {
|
|
141
|
+
if (typeof dir !== "string" || dir.length === 0) {
|
|
142
|
+
throw new Error("ledgerDir must be a non-empty string");
|
|
143
|
+
}
|
|
144
|
+
if (dir.includes("\0")) {
|
|
145
|
+
throw new Error("ledgerDir must not contain null bytes");
|
|
146
|
+
}
|
|
147
|
+
if (!path.isAbsolute(dir)) {
|
|
148
|
+
throw new Error(`ledgerDir must be an absolute path; got: ${dir}`);
|
|
149
|
+
}
|
|
150
|
+
try {
|
|
151
|
+
const st = lstatSync(dir);
|
|
152
|
+
if (st.isSymbolicLink()) {
|
|
153
|
+
throw new Error(`ledgerDir must not be a symlink: ${dir}`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
catch (err) {
|
|
157
|
+
const code = err.code;
|
|
158
|
+
if (code === "ENOENT")
|
|
159
|
+
return; // dir will be created later — fine
|
|
160
|
+
throw err;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Detect a tool result that reports failure by return value rather than
|
|
165
|
+
* by throwing. Connector write tools wrap transient errors as
|
|
166
|
+
* `JSON.stringify({ok:false, error})`; such a result must NOT be cached
|
|
167
|
+
* in the effect ledger (the side effect didn't happen, so retry should
|
|
168
|
+
* re-execute).
|
|
169
|
+
*
|
|
170
|
+
* Robustness: only a JSON object literal with an explicit `ok === false`
|
|
171
|
+
* counts as a failure. `null`, non-JSON strings (`JSON.parse` throws),
|
|
172
|
+
* JSON primitives/arrays, and objects without `ok === false` (e.g.
|
|
173
|
+
* `{ok:true}`) are all treated as genuine successes and cache normally.
|
|
174
|
+
*/
|
|
175
|
+
function isReturnValueFailure(result) {
|
|
176
|
+
if (result === null)
|
|
177
|
+
return false;
|
|
178
|
+
let parsed;
|
|
179
|
+
try {
|
|
180
|
+
parsed = JSON.parse(result);
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
// Non-JSON string output — a genuine success (e.g. "Posted to #x").
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
return (typeof parsed === "object" &&
|
|
187
|
+
parsed !== null &&
|
|
188
|
+
!Array.isArray(parsed) &&
|
|
189
|
+
parsed.ok === false);
|
|
190
|
+
}
|
|
191
|
+
export class WriteEffectLedger {
|
|
192
|
+
cache = new Map();
|
|
193
|
+
inFlight = new Map();
|
|
194
|
+
disk;
|
|
195
|
+
file;
|
|
196
|
+
constructor(disk) {
|
|
197
|
+
if (disk) {
|
|
198
|
+
// Validate + normalise the directory before any IO. Rejects null
|
|
199
|
+
// bytes (would short-circuit C string handling in libc paths),
|
|
200
|
+
// requires absolute paths (relative paths resolve against cwd
|
|
201
|
+
// which is the recipe workspace — surprising and racy), and
|
|
202
|
+
// refuses symlinks (a symlink swap on the ledger directory after
|
|
203
|
+
// construction could redirect appends to an attacker-chosen
|
|
204
|
+
// path).
|
|
205
|
+
assertSafeLedgerDir(disk.dir);
|
|
206
|
+
}
|
|
207
|
+
this.disk = disk ?? null;
|
|
208
|
+
this.file = disk ? path.join(disk.dir, LEDGER_FILENAME) : null;
|
|
209
|
+
if (this.disk && this.file) {
|
|
210
|
+
try {
|
|
211
|
+
mkdirSync(this.disk.dir, { recursive: true, mode: 0o700 });
|
|
212
|
+
}
|
|
213
|
+
catch (err) {
|
|
214
|
+
this.disk.logger?.warn?.(`[effect-ledger] could not create ${this.disk.dir}: ${err instanceof Error ? err.message : String(err)}`);
|
|
215
|
+
}
|
|
216
|
+
this.loadExisting();
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
has(key) {
|
|
220
|
+
return this.cache.has(key);
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Return the previously-cached output for `key`, or `undefined` if not
|
|
224
|
+
* recorded. `null` is a legitimate cached value (= the tool returned
|
|
225
|
+
* `null` originally), so callers must use `has()` to distinguish "not
|
|
226
|
+
* present" from "present and null".
|
|
227
|
+
*/
|
|
228
|
+
get(key) {
|
|
229
|
+
return this.cache.get(key);
|
|
230
|
+
}
|
|
231
|
+
record(key, output) {
|
|
232
|
+
this.cache.set(key, output);
|
|
233
|
+
if (this.disk && this.file) {
|
|
234
|
+
this.append({
|
|
235
|
+
scopeKey: this.disk.scopeKey,
|
|
236
|
+
idemKey: key,
|
|
237
|
+
output,
|
|
238
|
+
recordedAt: Date.now(),
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Atomically check cache + in-flight map, then execute `fn` at most once
|
|
244
|
+
* per key. Concurrent callers with the same key share a single Promise —
|
|
245
|
+
* fixing the TOCTOU window that existed between `has()` and `record()`.
|
|
246
|
+
*
|
|
247
|
+
* Failures are not cached so retry-after-failure still re-executes. A
|
|
248
|
+
* failure is EITHER a rejected promise OR a resolved result that is a
|
|
249
|
+
* JSON-encoded `{ok:false, ...}` envelope. The latter case matters:
|
|
250
|
+
* connector write tools (e.g. `asana.create_task`) catch transient
|
|
251
|
+
* errors internally and RETURN `JSON.stringify({ok:false, error})`
|
|
252
|
+
* rather than throwing. If we cached that, the next retry would replay
|
|
253
|
+
* the cached failure as a silent no-op instead of re-executing — which
|
|
254
|
+
* is the entire class of "write tools that report failure by return
|
|
255
|
+
* value". So we parse the resolved result and treat `ok === false` as a
|
|
256
|
+
* failure: drop the in-flight entry, return the result to THIS caller,
|
|
257
|
+
* but do NOT record it, so a subsequent retry runs `fn` again.
|
|
258
|
+
*
|
|
259
|
+
* Genuine successes still cache: non-JSON strings (`JSON.parse` throws
|
|
260
|
+
* SyntaxError), `null`, and any JSON value without `ok === false` (e.g.
|
|
261
|
+
* `{ok:true}`) are recorded as before.
|
|
262
|
+
*/
|
|
263
|
+
getOrExecute(key, fn) {
|
|
264
|
+
if (this.cache.has(key)) {
|
|
265
|
+
return Promise.resolve(this.cache.get(key) ?? null);
|
|
266
|
+
}
|
|
267
|
+
const existing = this.inFlight.get(key);
|
|
268
|
+
if (existing)
|
|
269
|
+
return existing;
|
|
270
|
+
const work = fn().then((result) => {
|
|
271
|
+
this.inFlight.delete(key);
|
|
272
|
+
// Don't cache return-value failures — a resolved {ok:false}
|
|
273
|
+
// envelope means the side effect did NOT happen, so retry must
|
|
274
|
+
// re-execute. Return the result to this caller without recording.
|
|
275
|
+
if (!isReturnValueFailure(result)) {
|
|
276
|
+
this.record(key, result);
|
|
277
|
+
}
|
|
278
|
+
return result;
|
|
279
|
+
}, (err) => {
|
|
280
|
+
this.inFlight.delete(key);
|
|
281
|
+
throw err;
|
|
282
|
+
});
|
|
283
|
+
this.inFlight.set(key, work);
|
|
284
|
+
return work;
|
|
285
|
+
}
|
|
286
|
+
/** Test-only inspection of the current key set. */
|
|
287
|
+
keys() {
|
|
288
|
+
return Array.from(this.cache.keys());
|
|
289
|
+
}
|
|
290
|
+
size() {
|
|
291
|
+
return this.cache.size;
|
|
292
|
+
}
|
|
293
|
+
loadExisting() {
|
|
294
|
+
if (!this.disk || !this.file)
|
|
295
|
+
return;
|
|
296
|
+
let raw;
|
|
297
|
+
try {
|
|
298
|
+
// `lstat` (not `stat`) so we see the symlink, not its target.
|
|
299
|
+
// A swapped symlink at `${dir}/effect_ledger.jsonl` would
|
|
300
|
+
// otherwise let an attacker substitute another file's contents
|
|
301
|
+
// as cached tool outputs.
|
|
302
|
+
const st = lstatSync(this.file);
|
|
303
|
+
if (st.isSymbolicLink()) {
|
|
304
|
+
this.disk.logger?.warn?.(`[effect-ledger] refusing to load ${this.file}: file is a symlink`);
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
raw = readFileSync(this.file, "utf-8");
|
|
308
|
+
}
|
|
309
|
+
catch (err) {
|
|
310
|
+
const code = err.code;
|
|
311
|
+
if (code !== "ENOENT") {
|
|
312
|
+
this.disk.logger?.warn?.(`[effect-ledger] read failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
313
|
+
}
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
for (const line of raw.split("\n")) {
|
|
317
|
+
if (!line)
|
|
318
|
+
continue;
|
|
319
|
+
try {
|
|
320
|
+
const row = JSON.parse(line);
|
|
321
|
+
if (typeof row.scopeKey !== "string" ||
|
|
322
|
+
typeof row.idemKey !== "string") {
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
if (row.scopeKey === this.disk.scopeKey) {
|
|
326
|
+
this.cache.set(row.idemKey, row.output ?? null);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
catch {
|
|
330
|
+
/* skip malformed row */
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
append(row) {
|
|
335
|
+
if (!this.disk || !this.file)
|
|
336
|
+
return;
|
|
337
|
+
try {
|
|
338
|
+
try {
|
|
339
|
+
const st = statSync(this.file);
|
|
340
|
+
if (st.size > MAX_PERSIST_BYTES)
|
|
341
|
+
this.rotate();
|
|
342
|
+
}
|
|
343
|
+
catch (err) {
|
|
344
|
+
const code = err.code;
|
|
345
|
+
if (code !== "ENOENT")
|
|
346
|
+
throw err;
|
|
347
|
+
}
|
|
348
|
+
appendFileSync(this.file, `${JSON.stringify(row)}\n`, { mode: 0o600 });
|
|
349
|
+
}
|
|
350
|
+
catch (err) {
|
|
351
|
+
this.disk.logger?.warn?.(`[effect-ledger] append failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Trim `effect_ledger.jsonl` to the most recent MAX_PERSIST_LINES.
|
|
356
|
+
* Best-effort — failure logs and the next append proceeds against the
|
|
357
|
+
* un-rotated file. Same pattern as RecipeRunLog / DecisionTraceLog.
|
|
358
|
+
*/
|
|
359
|
+
rotate() {
|
|
360
|
+
if (!this.file || !this.disk)
|
|
361
|
+
return;
|
|
362
|
+
try {
|
|
363
|
+
const raw = readFileSync(this.file, "utf-8");
|
|
364
|
+
let lines = raw.split("\n").filter((l) => l.trim());
|
|
365
|
+
if (lines.length > MAX_PERSIST_LINES) {
|
|
366
|
+
lines = lines.slice(-MAX_PERSIST_LINES);
|
|
367
|
+
}
|
|
368
|
+
writeFileAtomicSync(this.file, lines.length > 0 ? `${lines.join("\n")}\n` : "", { mode: 0o600 });
|
|
369
|
+
}
|
|
370
|
+
catch (err) {
|
|
371
|
+
this.disk.logger?.warn?.(`[effect-ledger] rotate failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
//# sourceMappingURL=idempotencyKey.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"idempotencyKey.js","sourceRoot":"","sources":["../../src/recipes/idempotencyKey.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,8 +1,40 @@
|
|
|
1
|
-
import
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import { mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import { parse as parseYaml } from "yaml";
|
|
4
5
|
import { compileRecipeFull } from "./compiler.js";
|
|
5
6
|
import { parseRecipe } from "./parser.js";
|
|
7
|
+
/**
|
|
8
|
+
* Atomic temp+rename for the default install write. Audit 2026-05-17:
|
|
9
|
+
* two concurrent installs of the same recipe (cross-process, e.g.
|
|
10
|
+
* dashboard + CLI racing) used to interleave bytes within the JSON
|
|
11
|
+
* payload because the previous `writeFileSync(destPath, ...)` is not
|
|
12
|
+
* atomic for sub-page writes. A torn JSON file fails to parse and the
|
|
13
|
+
* recipe becomes invisible to the scheduler.
|
|
14
|
+
*
|
|
15
|
+
* `rename` is atomic at the FS layer on every platform we ship on
|
|
16
|
+
* (apfs / ext4 / ntfs / xfs). With temp+rename, two concurrent writers
|
|
17
|
+
* each end up with their own intact file → last `rename` wins, but
|
|
18
|
+
* the file on disk is ALWAYS a valid JSON document.
|
|
19
|
+
*/
|
|
20
|
+
function atomicWriteSync(target, content) {
|
|
21
|
+
const tmp = `${target}.tmp.${process.pid}.${crypto
|
|
22
|
+
.randomBytes(6)
|
|
23
|
+
.toString("hex")}`;
|
|
24
|
+
try {
|
|
25
|
+
writeFileSync(tmp, content);
|
|
26
|
+
renameSync(tmp, target);
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
try {
|
|
30
|
+
unlinkSync(tmp);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
/* already gone or never created */
|
|
34
|
+
}
|
|
35
|
+
throw err;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
6
38
|
export function installRecipeFromFile(sourcePath, opts) {
|
|
7
39
|
const ext = path.extname(sourcePath).toLowerCase();
|
|
8
40
|
if (ext !== ".json" && ext !== ".yaml" && ext !== ".yml") {
|
|
@@ -10,7 +42,10 @@ export function installRecipeFromFile(sourcePath, opts) {
|
|
|
10
42
|
}
|
|
11
43
|
const fs = opts.fs ?? {};
|
|
12
44
|
const readFile = fs.readFile ?? ((p) => readFileSync(p, "utf-8"));
|
|
13
|
-
|
|
45
|
+
// Default writer is atomic (temp+rename). Callers may inject their
|
|
46
|
+
// own writeFile (tests, in-memory fs); they are responsible for
|
|
47
|
+
// their own atomicity guarantees.
|
|
48
|
+
const writeFile = fs.writeFile ?? atomicWriteSync;
|
|
14
49
|
const mkdir = fs.mkdir ?? ((p) => mkdirSync(p, { recursive: true }));
|
|
15
50
|
const text = readFile(sourcePath);
|
|
16
51
|
const raw = ext === ".json"
|
|
@@ -25,9 +60,18 @@ export function installRecipeFromFile(sourcePath, opts) {
|
|
|
25
60
|
// they fire via the CLI, RecipeScheduler, and POST /hooks/* endpoint
|
|
26
61
|
// respectively. Synthesize an empty CompiledRecipe stub so the caller API
|
|
27
62
|
// stays uniform; file_watch and git_hook still run through compile.
|
|
63
|
+
//
|
|
64
|
+
// `chained`, `on_file_save`, and `on_test_run` are runtime-only triggers
|
|
65
|
+
// (ChainedRecipeRunner / yamlRunner + the bridge's automation hooks) that
|
|
66
|
+
// the recipe→AutomationProgram compiler does not model. They must bypass
|
|
67
|
+
// compile too — otherwise mapTrigger() throws "unknown trigger type" and
|
|
68
|
+
// a recipe that lints + runs cannot be installed (the original bug).
|
|
28
69
|
const bypassCompile = recipe.trigger.type === "manual" ||
|
|
29
70
|
recipe.trigger.type === "cron" ||
|
|
30
|
-
recipe.trigger.type === "webhook"
|
|
71
|
+
recipe.trigger.type === "webhook" ||
|
|
72
|
+
recipe.trigger.type === "chained" ||
|
|
73
|
+
recipe.trigger.type === "on_file_save" ||
|
|
74
|
+
recipe.trigger.type === "on_test_run";
|
|
31
75
|
const compiled = bypassCompile
|
|
32
76
|
? {
|
|
33
77
|
program: {
|
|
@@ -39,6 +83,17 @@ export function installRecipeFromFile(sourcePath, opts) {
|
|
|
39
83
|
: compileRecipeFull(recipe);
|
|
40
84
|
mkdir(opts.recipesDir);
|
|
41
85
|
const destPath = path.join(opts.recipesDir, `${recipe.name}.json`);
|
|
86
|
+
// Belt-and-braces: parseRecipe already constrains `name` to the
|
|
87
|
+
// RECIPE_NAME_RE charset, but assert the resolved path stays inside
|
|
88
|
+
// recipesDir before writing. Defends against any future parser bypass
|
|
89
|
+
// and against callers reaching this function with a pre-parsed object
|
|
90
|
+
// that skipped parseRecipe.
|
|
91
|
+
const resolvedDir = path.resolve(opts.recipesDir);
|
|
92
|
+
const resolvedDest = path.resolve(destPath);
|
|
93
|
+
if (resolvedDest !== resolvedDir &&
|
|
94
|
+
!resolvedDest.startsWith(resolvedDir + path.sep)) {
|
|
95
|
+
throw new Error(`installRecipeFromFile: refusing to write outside recipesDir (dest=${resolvedDest} dir=${resolvedDir})`);
|
|
96
|
+
}
|
|
42
97
|
let action = "created";
|
|
43
98
|
try {
|
|
44
99
|
readFile(destPath);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"installer.js","sourceRoot":"","sources":["../../src/recipes/installer.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"installer.js","sourceRoot":"","sources":["../../src/recipes/installer.ts"],"names":[],"mappings":"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"}
|