patchwork-os 0.2.0-beta.1 → 0.2.0-beta.10.canary.95
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 +202 -25
- 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/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 +110 -0
- package/dist/commands/recipe.js +512 -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.js +2 -2
- 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/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 +866 -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 +63 -0
- package/dist/connectors/connectorRegistry.js +354 -0
- package/dist/connectors/connectorRegistry.js.map +1 -0
- package/dist/connectors/datadog.js +33 -4
- 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 +227 -1
- 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/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/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 +571 -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 +615 -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.js +15 -6
- 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 +5 -4
- 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 +22 -3
- 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 +17 -0
- package/dist/drivers/local/index.js +99 -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 +217 -82
- 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 +29 -1
- 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 +1053 -76
- package/dist/index.js.map +1 -1
- package/dist/installGuard.js +6 -2
- package/dist/installGuard.js.map +1 -1
- 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/childBridgeRegistry.js +29 -11
- package/dist/orchestrator/childBridgeRegistry.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 +142 -5
- package/dist/recipes/chainedRunner.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.js +13 -5
- package/dist/recipes/dependencyGraph.js.map +1 -1
- package/dist/recipes/githubInstallSource.d.ts +128 -0
- package/dist/recipes/githubInstallSource.js +206 -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 +134 -0
- package/dist/recipes/idempotencyKey.js +322 -0
- package/dist/recipes/idempotencyKey.js.map +1 -0
- package/dist/recipes/installer.js +48 -2
- 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 +88 -5
- 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 +36 -0
- package/dist/recipes/schemaGenerator.js +103 -5
- 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 +20 -3
- package/dist/recipes/toolRegistry.js.map +1 -1
- 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 +2 -0
- package/dist/recipes/tools/index.js +2 -0
- package/dist/recipes/tools/index.js.map +1 -1
- package/dist/recipes/tools/slack.js +1 -1
- package/dist/recipes/validation.d.ts +17 -0
- package/dist/recipes/validation.js +29 -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 +182 -8
- package/dist/recipes/yamlRunner.js +877 -217
- 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 +885 -196
- 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 +19 -12
- 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/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 +79 -202
- 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 +3 -0
- package/dist/tools/github/issues.d.ts +4 -0
- package/dist/tools/github/pr.d.ts +7 -0
- 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/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 +15 -4
- 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 +17 -7
- package/dist/tools/searchAndReplace.js.map +1 -1
- package/dist/tools/searchTools.js +20 -19
- 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 +4 -0
- package/dist/tools/transaction.js +4 -1
- package/dist/tools/transaction.js.map +1 -1
- package/dist/tools/typeHierarchy.d.ts +1 -0
- package/dist/tools/utils.d.ts +18 -0
- package/dist/tools/utils.js +103 -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 +103 -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
package/dist/featureFlags.js
CHANGED
|
@@ -7,9 +7,11 @@
|
|
|
7
7
|
* - Kill switch for write-tier operations
|
|
8
8
|
* - Per-feature opt-in with default-off safety
|
|
9
9
|
*/
|
|
10
|
-
import { existsSync, mkdirSync, readFileSync
|
|
10
|
+
import { existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
11
11
|
import os from "node:os";
|
|
12
12
|
import { join } from "node:path";
|
|
13
|
+
import { watchDirectoryWithFallback } from "./fsWatchWithFallback.js";
|
|
14
|
+
import { writeFileAtomicSync } from "./writeFileAtomic.js";
|
|
13
15
|
/** Flag registry — all known flags */
|
|
14
16
|
const FLAG_REGISTRY = new Map();
|
|
15
17
|
/** Runtime flag values (after env/file resolution) */
|
|
@@ -88,6 +90,7 @@ export function isEnabled(flagId) {
|
|
|
88
90
|
const frozen = FROZEN_KILL_SWITCH_ENV.get(flagId);
|
|
89
91
|
if (frozen !== undefined)
|
|
90
92
|
return frozen;
|
|
93
|
+
// biome-ignore lint/style/noNonNullAssertion: flag presence verified by the outer `flag !== undefined` guard
|
|
91
94
|
return FLAG_VALUES.get(flagId);
|
|
92
95
|
}
|
|
93
96
|
// Dynamic env read for non-kill-switch flags (test-friendly).
|
|
@@ -96,18 +99,91 @@ export function isEnabled(flagId) {
|
|
|
96
99
|
if (envVal !== undefined) {
|
|
97
100
|
return envVal === "1" || envVal.toLowerCase() === "true";
|
|
98
101
|
}
|
|
102
|
+
// biome-ignore lint/style/noNonNullAssertion: flag presence verified by the outer `flag !== undefined` guard
|
|
99
103
|
return FLAG_VALUES.get(flagId);
|
|
100
104
|
}
|
|
101
105
|
// Unknown flag — default to false (safe)
|
|
102
106
|
return false;
|
|
103
107
|
}
|
|
108
|
+
/**
|
|
109
|
+
* Returns true when the given kill-switch flag is **env-locked** — i.e. its
|
|
110
|
+
* value was frozen at startup from a `PATCHWORK_FLAG_<ID>` environment
|
|
111
|
+
* variable and any subsequent `setFlag()` call will be silently overridden
|
|
112
|
+
* by `isEnabled()`.
|
|
113
|
+
*
|
|
114
|
+
* Used by the `/kill-switch` endpoint (issue #422) to surface a 409
|
|
115
|
+
* Conflict instead of returning 200 OK for a setFlag that won't stick,
|
|
116
|
+
* and by the dashboard to render the toggle as disabled (with a tooltip
|
|
117
|
+
* naming which direction was sysadmin-locked) when this returns true.
|
|
118
|
+
*
|
|
119
|
+
* Returns false for non-kill-switch flags (they read env dynamically and
|
|
120
|
+
* are never "locked"), for unknown flags, and when `lockKillSwitchEnv()`
|
|
121
|
+
* has not yet been called.
|
|
122
|
+
*/
|
|
123
|
+
export function isEnvLockedFor(flagId) {
|
|
124
|
+
if (!envLocked)
|
|
125
|
+
return false;
|
|
126
|
+
const flag = FLAG_REGISTRY.get(flagId);
|
|
127
|
+
if (!flag?.isKillSwitch)
|
|
128
|
+
return false;
|
|
129
|
+
return FROZEN_KILL_SWITCH_ENV.get(flagId) !== undefined;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Returns the frozen env-locked value for a kill-switch flag (`true` /
|
|
133
|
+
* `false`), or `null` if not env-locked.
|
|
134
|
+
*
|
|
135
|
+
* Used by the dashboard tooltip so the disabled-state can read "env-locked
|
|
136
|
+
* to **on**" vs "env-locked to **off**" — both directions are policy-locked,
|
|
137
|
+
* but the user should know which.
|
|
138
|
+
*/
|
|
139
|
+
export function getEnvLockedValue(flagId) {
|
|
140
|
+
if (!isEnvLockedFor(flagId))
|
|
141
|
+
return null;
|
|
142
|
+
const frozen = FROZEN_KILL_SWITCH_ENV.get(flagId);
|
|
143
|
+
return frozen === undefined ? null : frozen;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Thrown by `setFlag` when a kill-switch flag is env-locked (its value was
|
|
147
|
+
* frozen at startup via `PATCHWORK_FLAG_<ID>`) and the caller attempts to
|
|
148
|
+
* mutate it. The `/kill-switch` POST handler catches this and returns a
|
|
149
|
+
* structured 409 Conflict so the CLI / dashboard can distinguish
|
|
150
|
+
* "policy-locked" from "bad request" (issue #422, pitfall I9).
|
|
151
|
+
*
|
|
152
|
+
* `frozenValue` is the locked direction — callers can surface
|
|
153
|
+
* "env-locked to on" vs "env-locked to off" in error messages.
|
|
154
|
+
*/
|
|
155
|
+
export class EnvLockedFlagError extends Error {
|
|
156
|
+
flagId;
|
|
157
|
+
frozenValue;
|
|
158
|
+
constructor(flagId, frozenValue) {
|
|
159
|
+
super(`Feature flag "${flagId}" is env-locked to ${frozenValue ? "true" : "false"} ` +
|
|
160
|
+
`(set at startup via PATCHWORK_FLAG_${flagId.replace(/[.-]/g, "_").toUpperCase()}). ` +
|
|
161
|
+
`Unset the environment variable to allow runtime mutation.`);
|
|
162
|
+
this.name = "EnvLockedFlagError";
|
|
163
|
+
this.flagId = flagId;
|
|
164
|
+
this.frozenValue = frozenValue;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
104
167
|
/**
|
|
105
168
|
* Set a flag value (persists to config file if persist=true).
|
|
169
|
+
*
|
|
170
|
+
* Throws `EnvLockedFlagError` if the flag is a kill-switch flag that has been
|
|
171
|
+
* frozen by `lockKillSwitchEnv()` — a sysadmin env-var override takes
|
|
172
|
+
* precedence over runtime mutation. The `/kill-switch` POST handler catches
|
|
173
|
+
* this and returns 409 (pitfall I9 from issue #422).
|
|
106
174
|
*/
|
|
107
175
|
export function setFlag(flagId, value, persist = false) {
|
|
108
176
|
if (!FLAG_REGISTRY.has(flagId)) {
|
|
109
177
|
throw new Error(`Unknown feature flag: "${flagId}"`);
|
|
110
178
|
}
|
|
179
|
+
// v2-I9: kill-switch flags that were env-locked at startup must not be
|
|
180
|
+
// silently overridden — throw so callers (the /kill-switch handler) can
|
|
181
|
+
// surface a structured 409 instead of returning 200 for a mutation that
|
|
182
|
+
// won't take effect.
|
|
183
|
+
if (isEnvLockedFor(flagId)) {
|
|
184
|
+
const frozen = FROZEN_KILL_SWITCH_ENV.get(flagId);
|
|
185
|
+
throw new EnvLockedFlagError(flagId, frozen === true);
|
|
186
|
+
}
|
|
111
187
|
FLAG_VALUES.set(flagId, value);
|
|
112
188
|
if (persist) {
|
|
113
189
|
persistFlags();
|
|
@@ -160,7 +236,7 @@ function persistFlags() {
|
|
|
160
236
|
toSave[id] = value;
|
|
161
237
|
}
|
|
162
238
|
}
|
|
163
|
-
|
|
239
|
+
writeFileAtomicSync(path, JSON.stringify(toSave, null, 2));
|
|
164
240
|
}
|
|
165
241
|
// ============================================================================
|
|
166
242
|
// Built-in Flags
|
|
@@ -169,6 +245,16 @@ function persistFlags() {
|
|
|
169
245
|
export const KILL_SWITCH_WRITES = "kill-switch.writes";
|
|
170
246
|
/** Enable recipe lint with schema validation (A1) */
|
|
171
247
|
export const FLAG_SCHEMA_LINT = "ui.schema-lint";
|
|
248
|
+
/**
|
|
249
|
+
* Phase 2A: gate the `/recipes/repair` LLM-driven fix endpoint.
|
|
250
|
+
* Default OFF — repair invokes the Claude orchestrator with a
|
|
251
|
+
* sanitized prompt built from the broken YAML + lint issues. While the
|
|
252
|
+
* prompt-injection sanitization is the same shape as `/recipes/generate`
|
|
253
|
+
* uses, the input here is the user's CURRENT recipe (potentially
|
|
254
|
+
* marketplace-installed = untrusted content), so we gate behind an
|
|
255
|
+
* explicit opt-in.
|
|
256
|
+
*/
|
|
257
|
+
export const FLAG_REPAIR_AI = "recipe.repair-ai";
|
|
172
258
|
// Register built-in flags
|
|
173
259
|
registerFlag({
|
|
174
260
|
id: KILL_SWITCH_WRITES,
|
|
@@ -185,8 +271,86 @@ registerFlag({
|
|
|
185
271
|
category: "ui",
|
|
186
272
|
requiresOptIn: true,
|
|
187
273
|
});
|
|
274
|
+
registerFlag({
|
|
275
|
+
id: FLAG_REPAIR_AI,
|
|
276
|
+
description: "Phase 2A: enable the /recipes/repair LLM-driven fix endpoint. Costs API tokens per call; gated by a per-session token bucket and prompt-injection sanitization.",
|
|
277
|
+
defaultValue: false,
|
|
278
|
+
category: "ui",
|
|
279
|
+
requiresOptIn: true,
|
|
280
|
+
});
|
|
188
281
|
// Load persisted flags on module init
|
|
189
282
|
loadFlags();
|
|
283
|
+
/**
|
|
284
|
+
* Watch the flags.json file for cross-process changes. When another
|
|
285
|
+
* process (typically the `patchwork kill-switch` CLI in its fallback
|
|
286
|
+
* fs-write path, or a sibling bridge in a multi-bridge deployment)
|
|
287
|
+
* writes to flags.json, this watcher reloads the in-memory FLAG_VALUES
|
|
288
|
+
* so the running bridge picks up the new state without a restart.
|
|
289
|
+
*
|
|
290
|
+
* v2-S1 + v2-B2 from #422. Closes the "no bridge reachable → CLI
|
|
291
|
+
* silent fallback → recipes keep writing" gap that motivated the
|
|
292
|
+
* redesign — even when the CLI's HTTP path fails and it falls back
|
|
293
|
+
* to writing the file directly, the running bridge still sees the
|
|
294
|
+
* change.
|
|
295
|
+
*
|
|
296
|
+
* **Env-lock interaction:** if `lockKillSwitchEnv()` already froze a
|
|
297
|
+
* kill-switch value from `PATCHWORK_FLAG_*`, the file-watch flow
|
|
298
|
+
* still updates FLAG_VALUES, but `isEnabled` continues reading from
|
|
299
|
+
* the frozen snapshot for that flag (existing behavior at L117-121).
|
|
300
|
+
* The env-lock is the source of truth — file changes can't override
|
|
301
|
+
* a sysadmin-mandated kill-switch state. This is the correct policy.
|
|
302
|
+
*
|
|
303
|
+
* Modeled on `src/pluginWatcher.ts`: directory-watch + filename
|
|
304
|
+
* filter + 100ms debounce so coalesced events (rename+create+change
|
|
305
|
+
* on most filesystems) don't trigger N reloads. Returns a close
|
|
306
|
+
* handle. Tolerates the file or directory not yet existing.
|
|
307
|
+
*/
|
|
308
|
+
export function watchFlags(opts = {}) {
|
|
309
|
+
const flagsPath = getFlagsPath();
|
|
310
|
+
const flagsDir = join(flagsPath, "..");
|
|
311
|
+
const debounceMs = opts.debounceMs ?? 100;
|
|
312
|
+
let debounceTimer = null;
|
|
313
|
+
let stopped = false;
|
|
314
|
+
const reload = () => {
|
|
315
|
+
if (debounceTimer)
|
|
316
|
+
clearTimeout(debounceTimer);
|
|
317
|
+
debounceTimer = setTimeout(() => {
|
|
318
|
+
debounceTimer = null;
|
|
319
|
+
if (stopped)
|
|
320
|
+
return;
|
|
321
|
+
try {
|
|
322
|
+
loadFlags();
|
|
323
|
+
}
|
|
324
|
+
catch {
|
|
325
|
+
// loadFlags has its own try/catch for parse errors;
|
|
326
|
+
// this catch is belt-and-suspenders for fs errors.
|
|
327
|
+
}
|
|
328
|
+
}, debounceMs);
|
|
329
|
+
};
|
|
330
|
+
// Watch the directory rather than the file directly — flags.json may not
|
|
331
|
+
// exist yet when watch is established, and editors / atomic writes
|
|
332
|
+
// (rename-into-place) lose direct file watches. The helper falls back to
|
|
333
|
+
// mtime polling when the dir is missing or fs.watch fails (Windows
|
|
334
|
+
// network drives, WSL bind mounts), and notices when the dir later appears.
|
|
335
|
+
const watchFn = opts.watcherFn ?? ((dir, cb) => watchDirectoryWithFallback(dir, cb));
|
|
336
|
+
const stopWatcher = watchFn(flagsDir, () => {
|
|
337
|
+
if (!stopped)
|
|
338
|
+
reload();
|
|
339
|
+
});
|
|
340
|
+
return () => {
|
|
341
|
+
stopped = true;
|
|
342
|
+
if (debounceTimer) {
|
|
343
|
+
clearTimeout(debounceTimer);
|
|
344
|
+
debounceTimer = null;
|
|
345
|
+
}
|
|
346
|
+
try {
|
|
347
|
+
stopWatcher();
|
|
348
|
+
}
|
|
349
|
+
catch {
|
|
350
|
+
/* ignore */
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
}
|
|
190
354
|
// ============================================================================
|
|
191
355
|
// Kill Switch Helpers
|
|
192
356
|
// ============================================================================
|
|
@@ -198,11 +362,18 @@ export function isWriteKillSwitchActive() {
|
|
|
198
362
|
}
|
|
199
363
|
/**
|
|
200
364
|
* Assert write is allowed — throws if kill switch is active.
|
|
365
|
+
*
|
|
366
|
+
* The thrown error carries `code: "kill_switch_blocked"` so the recipe
|
|
367
|
+
* runner can categorise the resulting halt as `kill_switch` (rather than
|
|
368
|
+
* a generic `tool_threw`) and the dashboard pill row can flag it
|
|
369
|
+
* distinctly from real tool failures.
|
|
201
370
|
*/
|
|
202
371
|
export function assertWriteAllowed(operation) {
|
|
203
372
|
if (isWriteKillSwitchActive()) {
|
|
204
|
-
|
|
373
|
+
const err = new Error(`Write operation blocked by kill switch: ${operation}. ` +
|
|
205
374
|
`Unset PATCHWORK_FLAG_KILL_SWITCH_WRITES or set kill-switch.writes=false to restore.`);
|
|
375
|
+
err.code = "kill_switch_blocked";
|
|
376
|
+
throw err;
|
|
206
377
|
}
|
|
207
378
|
}
|
|
208
379
|
//# sourceMappingURL=featureFlags.js.map
|
package/dist/featureFlags.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"featureFlags.js","sourceRoot":"","sources":["../src/featureFlags.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,
|
|
1
|
+
{"version":3,"file":"featureFlags.js","sourceRoot":"","sources":["../src/featureFlags.ts"],"names":[],"mappings":"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"}
|
package/dist/fileLock.js
CHANGED
|
@@ -39,10 +39,12 @@ export class FileLock {
|
|
|
39
39
|
if (holder !== sessionId) {
|
|
40
40
|
return { lockedBySession: holder };
|
|
41
41
|
}
|
|
42
|
-
// Same
|
|
43
|
-
//
|
|
42
|
+
// Same-session re-entry: return a no-op release so we don't overwrite
|
|
43
|
+
// the live lock-tail promise, which would break the FIFO chain for any
|
|
44
|
+
// subsequent acquire() caller that chains behind it.
|
|
45
|
+
return { release: () => { } };
|
|
44
46
|
}
|
|
45
|
-
// Lock is free
|
|
47
|
+
// Lock is free — grant it synchronously.
|
|
46
48
|
let release;
|
|
47
49
|
const next = new Promise((r) => {
|
|
48
50
|
release = r;
|
|
@@ -81,18 +83,23 @@ export class FileLock {
|
|
|
81
83
|
await Promise.race([prev, timeout]);
|
|
82
84
|
clearTimeout(timeoutId);
|
|
83
85
|
if (timedOut) {
|
|
84
|
-
//
|
|
85
|
-
//
|
|
86
|
-
//
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
86
|
+
// Bridge: resolve our slot only after the previous holder actually
|
|
87
|
+
// releases, so any waiter already chained behind us (awaiting `next`)
|
|
88
|
+
// gets the lock in FIFO order rather than immediately.
|
|
89
|
+
// Do NOT delete locks[path] here — the bridge needs it to stay so
|
|
90
|
+
// a new caller who arrives before prev resolves chains correctly.
|
|
91
|
+
void prev.then(() => {
|
|
92
|
+
release();
|
|
93
|
+
if (this.locks.get(path) === next)
|
|
94
|
+
this.locks.delete(path);
|
|
95
|
+
});
|
|
90
96
|
throw new Error(`Timed out waiting for file lock on "${path}" after ${this.timeoutMs / 1000}s — another session may be editing the same file`);
|
|
91
97
|
}
|
|
92
98
|
}
|
|
93
99
|
catch (err) {
|
|
94
100
|
clearTimeout(timeoutId);
|
|
95
|
-
|
|
101
|
+
// Do not delete the lock when timedOut — the bridge above will clean up.
|
|
102
|
+
if (!timedOut && this.locks.get(path) === next)
|
|
96
103
|
this.locks.delete(path);
|
|
97
104
|
throw err;
|
|
98
105
|
}
|
|
@@ -103,13 +110,15 @@ export class FileLock {
|
|
|
103
110
|
this.locks.delete(path);
|
|
104
111
|
throw new Error("Aborted before lock acquired");
|
|
105
112
|
}
|
|
106
|
-
// Clean up map entry after release so it doesn't grow forever
|
|
113
|
+
// Clean up map entry after release so it doesn't grow forever.
|
|
114
|
+
// acquire() does not own the holders map (only tryAcquire writes it),
|
|
115
|
+
// so we must not unconditionally delete a holders entry that a
|
|
116
|
+
// concurrent tryAcquire may have written.
|
|
107
117
|
const wrappedRelease = () => {
|
|
108
118
|
signal?.removeEventListener("abort", onAbort);
|
|
109
119
|
release();
|
|
110
120
|
if (this.locks.get(path) === next)
|
|
111
121
|
this.locks.delete(path);
|
|
112
|
-
this.holders.delete(path);
|
|
113
122
|
};
|
|
114
123
|
// If the holder is aborted while holding the lock, release automatically
|
|
115
124
|
// so downstream waiters are not blocked for the full timeout period.
|
package/dist/fileLock.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fileLock.js","sourceRoot":"","sources":["../src/fileLock.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,MAAM,uBAAuB,GAAG,MAAM,CAAC;AAOvC;;;GAGG;AACH,MAAM,OAAO,QAAQ;IACX,KAAK,GAAG,IAAI,GAAG,EAAyB,CAAC;IACjD,0EAA0E;IAClE,OAAO,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC3B,SAAS,CAAS;IAEnC,YAAY,SAAS,GAAG,uBAAuB;QAC7C,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC7B,CAAC;IAED;;;;;;;;;OASG;IACH;;;;;;OAMG;IACH,UAAU,CACR,IAAY,EACZ,SAAiB;QAEjB,sEAAsE;QACtE,wEAAwE;QACxE,yEAAyE;QACzE,uCAAuC;QACvC,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,SAAS,EAAE,CAAC;YACvC,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,iBAAiB,CAAC;YAC3D,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;gBACzB,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,CAAC;YACrC,CAAC;YACD,
|
|
1
|
+
{"version":3,"file":"fileLock.js","sourceRoot":"","sources":["../src/fileLock.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tiny cross-process file mutex for synchronous critical sections.
|
|
3
|
+
*
|
|
4
|
+
* Used to wrap append-only JSONL writers (`decisionTraceLog`,
|
|
5
|
+
* `runLog`, `commitIssueLinkLog`) so two bridge processes sharing
|
|
6
|
+
* one `~/.claude/ide/` log directory can't interleave bytes within a
|
|
7
|
+
* single row.
|
|
8
|
+
*
|
|
9
|
+
* Why ad-hoc instead of `proper-lockfile`? proper-lockfile is the more
|
|
10
|
+
* featureful option (TTL refresh, retry jitter, async API) but adds a
|
|
11
|
+
* runtime dependency for a use case the bridge needs only synchronously
|
|
12
|
+
* in three call-sites. The pattern below — `openSync(file, "wx")` as
|
|
13
|
+
* the atomic lock primitive plus a stale-lock cleanup heuristic — is
|
|
14
|
+
* standard for shell-style flock without a native binding.
|
|
15
|
+
*
|
|
16
|
+
* ## Semantics
|
|
17
|
+
*
|
|
18
|
+
* `withFileLockSync(file, fn)`:
|
|
19
|
+
*
|
|
20
|
+
* 1. Attempts to create `${file}.lock` with `O_EXCL` (`flag: "wx"`).
|
|
21
|
+
* First writer wins atomically — the kernel rejects EEXIST for
|
|
22
|
+
* every other contender.
|
|
23
|
+
* 2. On EEXIST, polls with a short busy-wait (`Atomics.wait` would
|
|
24
|
+
* need a shared SharedArrayBuffer across processes — out of scope;
|
|
25
|
+
* hot-loop with `process.hrtime` instead, kernels schedule sleep
|
|
26
|
+
* anyway under contention).
|
|
27
|
+
* 3. If the existing lock is older than `staleLockMs` (default 30s),
|
|
28
|
+
* assumes the owner crashed and force-unlinks it before retrying.
|
|
29
|
+
* The append-only JSONL writes the bridge does complete in <1ms in
|
|
30
|
+
* practice, so 30s is generous.
|
|
31
|
+
* 4. Runs `fn()`, unlinks the lock in `finally`. Rethrows fn's
|
|
32
|
+
* exception.
|
|
33
|
+
*
|
|
34
|
+
* Lock granularity is per-file (each log gets its own lock). At the
|
|
35
|
+
* bridge's write volume (≤ tens/min across all three logs) contention
|
|
36
|
+
* is effectively zero.
|
|
37
|
+
*
|
|
38
|
+
* ## Failure modes
|
|
39
|
+
*
|
|
40
|
+
* - **Deadlock-on-crash:** mitigated by the 30s stale-lock TTL. A bridge
|
|
41
|
+
* that crashes mid-append leaves the lock until the next contender
|
|
42
|
+
* notices it's stale and clears it. The first crash is invisible
|
|
43
|
+
* (target file already written); subsequent contenders pay a 30s
|
|
44
|
+
* penalty only if they happen to arrive within the window.
|
|
45
|
+
* - **TOCTOU on stale-lock unlink:** two processes both decide the lock
|
|
46
|
+
* is stale and both `unlinkSync`. Second `unlinkSync` ENOENT is
|
|
47
|
+
* swallowed. Both then race the `openSync("wx")` — one wins, the
|
|
48
|
+
* other goes back to polling. No data corruption.
|
|
49
|
+
* - **NFS / non-POSIX FS:** O_EXCL is undefined on stale NFSv2; modern
|
|
50
|
+
* NFS (≥ v3 with `noac`) and all local FS we ship on (apfs / ext4 /
|
|
51
|
+
* ntfs) implement it correctly.
|
|
52
|
+
*
|
|
53
|
+
* @param file - The target file the caller is about to write. The lock
|
|
54
|
+
* sentinel is `${file}.lock`.
|
|
55
|
+
* @param fn - Synchronous critical section. Holds the lock for its
|
|
56
|
+
* entire duration.
|
|
57
|
+
* @param opts.timeoutMs - Max wait for the lock before throwing (default
|
|
58
|
+
* 5000). Use a small value — appends finish in <1ms in practice; a
|
|
59
|
+
* timeout this far above the expected duration only fires if
|
|
60
|
+
* something genuinely wrong happens.
|
|
61
|
+
* @param opts.staleLockMs - Age (ms) at which an existing lock is
|
|
62
|
+
* considered abandoned and force-unlinked. Default 30000.
|
|
63
|
+
*/
|
|
64
|
+
export declare function withFileLockSync<T>(file: string, fn: () => T, opts?: {
|
|
65
|
+
timeoutMs?: number;
|
|
66
|
+
staleLockMs?: number;
|
|
67
|
+
}): T;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { closeSync, openSync, statSync, unlinkSync } from "node:fs";
|
|
2
|
+
/**
|
|
3
|
+
* Tiny cross-process file mutex for synchronous critical sections.
|
|
4
|
+
*
|
|
5
|
+
* Used to wrap append-only JSONL writers (`decisionTraceLog`,
|
|
6
|
+
* `runLog`, `commitIssueLinkLog`) so two bridge processes sharing
|
|
7
|
+
* one `~/.claude/ide/` log directory can't interleave bytes within a
|
|
8
|
+
* single row.
|
|
9
|
+
*
|
|
10
|
+
* Why ad-hoc instead of `proper-lockfile`? proper-lockfile is the more
|
|
11
|
+
* featureful option (TTL refresh, retry jitter, async API) but adds a
|
|
12
|
+
* runtime dependency for a use case the bridge needs only synchronously
|
|
13
|
+
* in three call-sites. The pattern below — `openSync(file, "wx")` as
|
|
14
|
+
* the atomic lock primitive plus a stale-lock cleanup heuristic — is
|
|
15
|
+
* standard for shell-style flock without a native binding.
|
|
16
|
+
*
|
|
17
|
+
* ## Semantics
|
|
18
|
+
*
|
|
19
|
+
* `withFileLockSync(file, fn)`:
|
|
20
|
+
*
|
|
21
|
+
* 1. Attempts to create `${file}.lock` with `O_EXCL` (`flag: "wx"`).
|
|
22
|
+
* First writer wins atomically — the kernel rejects EEXIST for
|
|
23
|
+
* every other contender.
|
|
24
|
+
* 2. On EEXIST, polls with a short busy-wait (`Atomics.wait` would
|
|
25
|
+
* need a shared SharedArrayBuffer across processes — out of scope;
|
|
26
|
+
* hot-loop with `process.hrtime` instead, kernels schedule sleep
|
|
27
|
+
* anyway under contention).
|
|
28
|
+
* 3. If the existing lock is older than `staleLockMs` (default 30s),
|
|
29
|
+
* assumes the owner crashed and force-unlinks it before retrying.
|
|
30
|
+
* The append-only JSONL writes the bridge does complete in <1ms in
|
|
31
|
+
* practice, so 30s is generous.
|
|
32
|
+
* 4. Runs `fn()`, unlinks the lock in `finally`. Rethrows fn's
|
|
33
|
+
* exception.
|
|
34
|
+
*
|
|
35
|
+
* Lock granularity is per-file (each log gets its own lock). At the
|
|
36
|
+
* bridge's write volume (≤ tens/min across all three logs) contention
|
|
37
|
+
* is effectively zero.
|
|
38
|
+
*
|
|
39
|
+
* ## Failure modes
|
|
40
|
+
*
|
|
41
|
+
* - **Deadlock-on-crash:** mitigated by the 30s stale-lock TTL. A bridge
|
|
42
|
+
* that crashes mid-append leaves the lock until the next contender
|
|
43
|
+
* notices it's stale and clears it. The first crash is invisible
|
|
44
|
+
* (target file already written); subsequent contenders pay a 30s
|
|
45
|
+
* penalty only if they happen to arrive within the window.
|
|
46
|
+
* - **TOCTOU on stale-lock unlink:** two processes both decide the lock
|
|
47
|
+
* is stale and both `unlinkSync`. Second `unlinkSync` ENOENT is
|
|
48
|
+
* swallowed. Both then race the `openSync("wx")` — one wins, the
|
|
49
|
+
* other goes back to polling. No data corruption.
|
|
50
|
+
* - **NFS / non-POSIX FS:** O_EXCL is undefined on stale NFSv2; modern
|
|
51
|
+
* NFS (≥ v3 with `noac`) and all local FS we ship on (apfs / ext4 /
|
|
52
|
+
* ntfs) implement it correctly.
|
|
53
|
+
*
|
|
54
|
+
* @param file - The target file the caller is about to write. The lock
|
|
55
|
+
* sentinel is `${file}.lock`.
|
|
56
|
+
* @param fn - Synchronous critical section. Holds the lock for its
|
|
57
|
+
* entire duration.
|
|
58
|
+
* @param opts.timeoutMs - Max wait for the lock before throwing (default
|
|
59
|
+
* 5000). Use a small value — appends finish in <1ms in practice; a
|
|
60
|
+
* timeout this far above the expected duration only fires if
|
|
61
|
+
* something genuinely wrong happens.
|
|
62
|
+
* @param opts.staleLockMs - Age (ms) at which an existing lock is
|
|
63
|
+
* considered abandoned and force-unlinked. Default 30000.
|
|
64
|
+
*/
|
|
65
|
+
export function withFileLockSync(file, fn, opts = {}) {
|
|
66
|
+
const lockFile = `${file}.lock`;
|
|
67
|
+
const timeoutMs = opts.timeoutMs ?? 5000;
|
|
68
|
+
const staleLockMs = opts.staleLockMs ?? 30_000;
|
|
69
|
+
const deadline = Date.now() + timeoutMs;
|
|
70
|
+
let lockFd = null;
|
|
71
|
+
while (lockFd === null) {
|
|
72
|
+
try {
|
|
73
|
+
// O_EXCL — atomic create; first caller wins.
|
|
74
|
+
lockFd = openSync(lockFile, "wx", 0o600);
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
const code = err.code;
|
|
78
|
+
if (code !== "EEXIST")
|
|
79
|
+
throw err;
|
|
80
|
+
// Stale-lock cleanup. Best-effort: another contender may unlink
|
|
81
|
+
// ours simultaneously; that race resolves benignly (next openSync
|
|
82
|
+
// either wins or sees EEXIST again).
|
|
83
|
+
try {
|
|
84
|
+
const stat = statSync(lockFile);
|
|
85
|
+
if (Date.now() - stat.mtimeMs > staleLockMs) {
|
|
86
|
+
try {
|
|
87
|
+
unlinkSync(lockFile);
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
/* already gone — another contender beat us to the cleanup */
|
|
91
|
+
}
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
// statSync ENOENT — lock cleared between EEXIST and stat; retry
|
|
97
|
+
// immediately.
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (Date.now() > deadline) {
|
|
101
|
+
throw new Error(`withFileLockSync: timed out after ${timeoutMs}ms waiting for ${lockFile}`);
|
|
102
|
+
}
|
|
103
|
+
// Busy-spin for ~5ms via hrtime. Node has no sync sleep; under
|
|
104
|
+
// contention the kernel preempts us, so this isn't actually a hot
|
|
105
|
+
// loop in practice (and tests can pass a 0ms timeout for the
|
|
106
|
+
// "already locked" case).
|
|
107
|
+
const spinUntil = process.hrtime.bigint() + 5000000n; // 5ms
|
|
108
|
+
while (process.hrtime.bigint() < spinUntil) {
|
|
109
|
+
/* yield */
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
try {
|
|
114
|
+
closeSync(lockFd);
|
|
115
|
+
return fn();
|
|
116
|
+
}
|
|
117
|
+
finally {
|
|
118
|
+
try {
|
|
119
|
+
unlinkSync(lockFile);
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
/* lock cleared by stale-lock cleanup in another process; ok */
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=fileLockSync.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fileLockSync.js","sourceRoot":"","sources":["../src/fileLockSync.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAEpE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8DG;AACH,MAAM,UAAU,gBAAgB,CAC9B,IAAY,EACZ,EAAW,EACX,OAAqD,EAAE;IAEvD,MAAM,QAAQ,GAAG,GAAG,IAAI,OAAO,CAAC;IAChC,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC;IACzC,MAAM,WAAW,GAAG,IAAI,CAAC,WAAW,IAAI,MAAM,CAAC;IAC/C,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;IAExC,IAAI,MAAM,GAAkB,IAAI,CAAC;IACjC,OAAO,MAAM,KAAK,IAAI,EAAE,CAAC;QACvB,IAAI,CAAC;YACH,6CAA6C;YAC7C,MAAM,GAAG,QAAQ,CAAC,QAAQ,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;QAC3C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,IAAI,GAAI,GAA6B,CAAC,IAAI,CAAC;YACjD,IAAI,IAAI,KAAK,QAAQ;gBAAE,MAAM,GAAG,CAAC;YACjC,gEAAgE;YAChE,kEAAkE;YAClE,qCAAqC;YACrC,IAAI,CAAC;gBACH,MAAM,IAAI,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC;gBAChC,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,OAAO,GAAG,WAAW,EAAE,CAAC;oBAC5C,IAAI,CAAC;wBACH,UAAU,CAAC,QAAQ,CAAC,CAAC;oBACvB,CAAC;oBAAC,MAAM,CAAC;wBACP,6DAA6D;oBAC/D,CAAC;oBACD,SAAS;gBACX,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,gEAAgE;gBAChE,eAAe;gBACf,SAAS;YACX,CAAC;YACD,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,EAAE,CAAC;gBAC1B,MAAM,IAAI,KAAK,CACb,qCAAqC,SAAS,kBAAkB,QAAQ,EAAE,CAC3E,CAAC;YACJ,CAAC;YACD,+DAA+D;YAC/D,kEAAkE;YAClE,6DAA6D;YAC7D,0BAA0B;YAC1B,MAAM,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,QAAU,CAAC,CAAC,MAAM;YAC9D,OAAO,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,SAAS,EAAE,CAAC;gBAC3C,WAAW;YACb,CAAC;QACH,CAAC;IACH,CAAC;IAED,IAAI,CAAC;QACH,SAAS,CAAC,MAAM,CAAC,CAAC;QAClB,OAAO,EAAE,EAAE,CAAC;IACd,CAAC;YAAS,CAAC;QACT,IAAI,CAAC;YACH,UAAU,CAAC,QAAQ,CAAC,CAAC;QACvB,CAAC;QAAC,MAAM,CAAC;YACP,+DAA+D;QACjE,CAAC;IACH,CAAC;AACH,CAAC"}
|
|
@@ -3,12 +3,15 @@ const CO_OCCURRENCE_MAP_CAP = 200;
|
|
|
3
3
|
export function computeStats(entries) {
|
|
4
4
|
const map = new Map();
|
|
5
5
|
for (const entry of entries) {
|
|
6
|
-
|
|
6
|
+
let s = map.get(entry.tool);
|
|
7
|
+
if (!s) {
|
|
8
|
+
s = { count: 0, totalMs: 0, errors: 0 };
|
|
9
|
+
map.set(entry.tool, s);
|
|
10
|
+
}
|
|
7
11
|
s.count++;
|
|
8
12
|
s.totalMs += entry.durationMs;
|
|
9
13
|
if (entry.status === "error")
|
|
10
14
|
s.errors++;
|
|
11
|
-
map.set(entry.tool, s);
|
|
12
15
|
}
|
|
13
16
|
const result = {};
|
|
14
17
|
for (const [tool, s] of map) {
|
|
@@ -53,17 +56,21 @@ export function computePercentiles(samples) {
|
|
|
53
56
|
export function computeCoOccurrence(entries, windowMs, maxPairs = 50) {
|
|
54
57
|
const counts = new Map();
|
|
55
58
|
const n = entries.length;
|
|
59
|
+
// Pre-compute epoch timestamps once to avoid repeated Date construction in nested loop.
|
|
60
|
+
const times = new Array(n);
|
|
61
|
+
for (let k = 0; k < n; k++) {
|
|
62
|
+
times[k] = Date.parse(entries[k]?.timestamp ?? "");
|
|
63
|
+
}
|
|
56
64
|
for (let i = 0; i < n; i++) {
|
|
57
65
|
const a = entries[i];
|
|
58
66
|
if (!a)
|
|
59
67
|
continue;
|
|
60
|
-
const tA =
|
|
68
|
+
const tA = times[i] ?? 0;
|
|
61
69
|
for (let j = i + 1; j < n; j++) {
|
|
62
70
|
const b = entries[j];
|
|
63
71
|
if (!b)
|
|
64
72
|
continue;
|
|
65
|
-
|
|
66
|
-
if (tB - tA > windowMs)
|
|
73
|
+
if ((times[j] ?? 0) - tA > windowMs)
|
|
67
74
|
break;
|
|
68
75
|
if (a.tool === b.tool)
|
|
69
76
|
continue;
|
|
@@ -73,10 +80,14 @@ export function computeCoOccurrence(entries, windowMs, maxPairs = 50) {
|
|
|
73
80
|
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
74
81
|
}
|
|
75
82
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
83
|
+
const pairs = Array.from(counts.entries());
|
|
84
|
+
pairs.sort((a, b) => b[1] - a[1]);
|
|
85
|
+
const result = [];
|
|
86
|
+
for (let i = 0; i < pairs.length && i < maxPairs; i++) {
|
|
87
|
+
const [pair, count] = pairs[i];
|
|
88
|
+
result.push({ pair, count });
|
|
89
|
+
}
|
|
90
|
+
return result;
|
|
80
91
|
}
|
|
81
92
|
/**
|
|
82
93
|
* Per-tool stats within a sliding time window.
|
|
@@ -89,14 +100,17 @@ export function computeWindowedStats(entries, windowMs, now) {
|
|
|
89
100
|
const e = entries[i];
|
|
90
101
|
if (!e)
|
|
91
102
|
continue;
|
|
92
|
-
if (
|
|
103
|
+
if (Date.parse(e.timestamp) < cutoff)
|
|
93
104
|
break;
|
|
94
|
-
|
|
105
|
+
let s = map.get(e.tool);
|
|
106
|
+
if (!s) {
|
|
107
|
+
s = { count: 0, totalMs: 0, errors: 0 };
|
|
108
|
+
map.set(e.tool, s);
|
|
109
|
+
}
|
|
95
110
|
s.count++;
|
|
96
111
|
s.totalMs += e.durationMs;
|
|
97
112
|
if (e.status === "error")
|
|
98
113
|
s.errors++;
|
|
99
|
-
map.set(e.tool, s);
|
|
100
114
|
}
|
|
101
115
|
const result = {};
|
|
102
116
|
for (const [tool, s] of map) {
|