@kbediako/codex-orchestrator 0.1.37 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/plugins/marketplace.json +20 -0
- package/README.md +73 -291
- package/bin/codex-orchestrator.js +161 -0
- package/codex.orchestrator.json +149 -13
- package/dist/bin/codex-orchestrator.js +795 -1154
- package/dist/orchestrator/src/cli/adapters/CommandPlanner.js +22 -4
- package/dist/orchestrator/src/cli/adapters/CommandReviewer.js +3 -3
- package/dist/orchestrator/src/cli/adapters/CommandTester.js +2 -2
- package/dist/orchestrator/src/cli/adapters/cloudFailureDiagnostics.js +183 -11
- package/dist/orchestrator/src/cli/coStatusAttachCliShell.js +402 -0
- package/dist/orchestrator/src/cli/coStatusCliShell.js +429 -0
- package/dist/orchestrator/src/cli/coStatusOperatorAutopilotCliShell.js +120 -0
- package/dist/orchestrator/src/cli/codexCliShell.js +72 -0
- package/dist/orchestrator/src/cli/codexDefaultsSetup.js +49 -11
- package/dist/orchestrator/src/cli/config/delegationConfig.js +317 -5
- package/dist/orchestrator/src/cli/config/repoConfigPolicy.js +2 -3
- package/dist/orchestrator/src/cli/config/userConfig.js +28 -13
- package/dist/orchestrator/src/cli/control/authenticatedControlRouteGate.js +69 -0
- package/dist/orchestrator/src/cli/control/authenticatedRouteComposition.js +267 -0
- package/dist/orchestrator/src/cli/control/authenticatedRouteController.js +5 -0
- package/dist/orchestrator/src/cli/control/authenticatedRouteDispatcher.js +41 -0
- package/dist/orchestrator/src/cli/control/compatibilityIssuePresenter.js +1035 -0
- package/dist/orchestrator/src/cli/control/confirmationApproveController.js +62 -0
- package/dist/orchestrator/src/cli/control/confirmationCreateController.js +69 -0
- package/dist/orchestrator/src/cli/control/confirmationIssueConsumeController.js +43 -0
- package/dist/orchestrator/src/cli/control/confirmationListController.js +22 -0
- package/dist/orchestrator/src/cli/control/confirmationValidateController.js +58 -0
- package/dist/orchestrator/src/cli/control/confirmations.js +25 -3
- package/dist/orchestrator/src/cli/control/controlActionCancelConfirmation.js +65 -0
- package/dist/orchestrator/src/cli/control/controlActionController.js +77 -0
- package/dist/orchestrator/src/cli/control/controlActionControllerSequencing.js +161 -0
- package/dist/orchestrator/src/cli/control/controlActionExecution.js +142 -0
- package/dist/orchestrator/src/cli/control/controlActionFinalization.js +43 -0
- package/dist/orchestrator/src/cli/control/controlActionOutcome.js +60 -0
- package/dist/orchestrator/src/cli/control/controlActionPreflight.js +476 -0
- package/dist/orchestrator/src/cli/control/controlAuthenticatedRouteHandoff.js +57 -0
- package/dist/orchestrator/src/cli/control/controlBootstrapAssembly.js +39 -0
- package/dist/orchestrator/src/cli/control/controlBootstrapMetadataPersistence.js +16 -0
- package/dist/orchestrator/src/cli/control/controlEventTransport.js +49 -0
- package/dist/orchestrator/src/cli/control/controlExpiryLifecycle.js +102 -0
- package/dist/orchestrator/src/cli/control/controlHostOwnership.js +480 -0
- package/dist/orchestrator/src/cli/control/controlHostSupervision.js +608 -0
- package/dist/orchestrator/src/cli/control/controlOversightFacade.js +8 -0
- package/dist/orchestrator/src/cli/control/controlOversightReadContract.js +1 -0
- package/dist/orchestrator/src/cli/control/controlOversightReadService.js +16 -0
- package/dist/orchestrator/src/cli/control/controlOversightUpdateContract.js +1 -0
- package/dist/orchestrator/src/cli/control/controlPersistenceFiles.js +6 -0
- package/dist/orchestrator/src/cli/control/controlQuestionChildResolution.js +18 -0
- package/dist/orchestrator/src/cli/control/controlRequestContext.js +42 -0
- package/dist/orchestrator/src/cli/control/controlRequestController.js +9 -0
- package/dist/orchestrator/src/cli/control/controlRequestPredispatch.js +17 -0
- package/dist/orchestrator/src/cli/control/controlRequestRouteDispatch.js +44 -0
- package/dist/orchestrator/src/cli/control/controlRuntime.js +992 -0
- package/dist/orchestrator/src/cli/control/controlServer.js +23 -1456
- package/dist/orchestrator/src/cli/control/controlServerAuditAndErrorHelpers.js +115 -0
- package/dist/orchestrator/src/cli/control/controlServerAuthenticatedRouteBranch.js +29 -0
- package/dist/orchestrator/src/cli/control/controlServerBootstrapLifecycle.js +30 -0
- package/dist/orchestrator/src/cli/control/controlServerBootstrapStartSequence.js +21 -0
- package/dist/orchestrator/src/cli/control/controlServerOwnedRuntimeLifecycle.js +67 -0
- package/dist/orchestrator/src/cli/control/controlServerPublicLifecycle.js +756 -0
- package/dist/orchestrator/src/cli/control/controlServerPublicRouteHelpers.js +86 -0
- package/dist/orchestrator/src/cli/control/controlServerReadyInstanceLifecycle.js +25 -0
- package/dist/orchestrator/src/cli/control/controlServerReadyInstanceStartup.js +18 -0
- package/dist/orchestrator/src/cli/control/controlServerRequestBodyHelpers.js +37 -0
- package/dist/orchestrator/src/cli/control/controlServerRequestShell.js +40 -0
- package/dist/orchestrator/src/cli/control/controlServerRequestShellBinding.js +17 -0
- package/dist/orchestrator/src/cli/control/controlServerSeedLoading.js +27 -0
- package/dist/orchestrator/src/cli/control/controlServerSeededRuntimeAssembly.js +186 -0
- package/dist/orchestrator/src/cli/control/controlServerStartupInputPreparation.js +31 -0
- package/dist/orchestrator/src/cli/control/controlServerStartupSequence.js +49 -0
- package/dist/orchestrator/src/cli/control/controlState.js +233 -2
- package/dist/orchestrator/src/cli/control/controlStatusDashboard.js +1899 -0
- package/dist/orchestrator/src/cli/control/controlTelegramBridgeBootstrapLifecycle.js +22 -0
- package/dist/orchestrator/src/cli/control/controlTelegramBridgeLifecycle.js +67 -0
- package/dist/orchestrator/src/cli/control/controlTelegramBridgeOversightFacadeFactory.js +8 -0
- package/dist/orchestrator/src/cli/control/controlTelegramCommandController.js +49 -0
- package/dist/orchestrator/src/cli/control/controlTelegramDispatchRead.js +40 -0
- package/dist/orchestrator/src/cli/control/controlTelegramPollingController.js +89 -0
- package/dist/orchestrator/src/cli/control/controlTelegramProjectionNotificationController.js +29 -0
- package/dist/orchestrator/src/cli/control/controlTelegramPushState.js +63 -0
- package/dist/orchestrator/src/cli/control/controlTelegramQuestionRead.js +13 -0
- package/dist/orchestrator/src/cli/control/controlTelegramReadController.js +216 -0
- package/dist/orchestrator/src/cli/control/controlTelegramUpdateHandler.js +63 -0
- package/dist/orchestrator/src/cli/control/controlWatcher.js +73 -5
- package/dist/orchestrator/src/cli/control/delegationRegisterController.js +35 -0
- package/dist/orchestrator/src/cli/control/dynamicToolBridgePolicy.js +139 -0
- package/dist/orchestrator/src/cli/control/eventsSseController.js +12 -0
- package/dist/orchestrator/src/cli/control/linearBudgetState.js +1789 -0
- package/dist/orchestrator/src/cli/control/linearDispatchSource.js +1137 -0
- package/dist/orchestrator/src/cli/control/linearGraphqlClient.js +150 -0
- package/dist/orchestrator/src/cli/control/linearRateLimit.js +102 -0
- package/dist/orchestrator/src/cli/control/linearWebhookController.js +499 -0
- package/dist/orchestrator/src/cli/control/liveLinearAdvisoryRuntime.js +70 -0
- package/dist/orchestrator/src/cli/control/observabilityApiController.js +173 -0
- package/dist/orchestrator/src/cli/control/observabilityReadModel.js +500 -0
- package/dist/orchestrator/src/cli/control/observabilitySurface.js +284 -0
- package/dist/orchestrator/src/cli/control/observabilityUpdateNotifier.js +22 -0
- package/dist/orchestrator/src/cli/control/operatorDashboardPresenter.js +252 -0
- package/dist/orchestrator/src/cli/control/providerAgentCapacity.js +70 -0
- package/dist/orchestrator/src/cli/control/providerControlHostFreshnessGauge.js +1068 -0
- package/dist/orchestrator/src/cli/control/providerIntakeState.js +473 -0
- package/dist/orchestrator/src/cli/control/providerIssueHandoff.js +6811 -0
- package/dist/orchestrator/src/cli/control/providerIssueObservability.js +1348 -0
- package/dist/orchestrator/src/cli/control/providerIssueRetryQueue.js +84 -0
- package/dist/orchestrator/src/cli/control/providerLinearRuntimeProof.js +588 -0
- package/dist/orchestrator/src/cli/control/providerLinearScreenshotProof.js +473 -0
- package/dist/orchestrator/src/cli/control/providerLinearWorkerTruth.js +383 -0
- package/dist/orchestrator/src/cli/control/providerLinearWorkflowAudit.js +254 -0
- package/dist/orchestrator/src/cli/control/providerLinearWorkflowFacade.js +5573 -0
- package/dist/orchestrator/src/cli/control/providerLinearWorkflowStates.js +115 -0
- package/dist/orchestrator/src/cli/control/providerMergeCloseout.js +1868 -0
- package/dist/orchestrator/src/cli/control/providerOperatorAutopilot.js +1580 -0
- package/dist/orchestrator/src/cli/control/providerOperatorAutopilotLifecycle.js +154 -0
- package/dist/orchestrator/src/cli/control/providerOperatorAutopilotLocalRolloutExecution.js +1006 -0
- package/dist/orchestrator/src/cli/control/providerPollingHealth.js +435 -0
- package/dist/orchestrator/src/cli/control/providerTerminalCleanup.js +516 -0
- package/dist/orchestrator/src/cli/control/providerWorkerHosts.js +191 -0
- package/dist/orchestrator/src/cli/control/providerWorkflowConfigStore.js +515 -0
- package/dist/orchestrator/src/cli/control/questionChildResolutionAdapter.js +361 -0
- package/dist/orchestrator/src/cli/control/questionQueueController.js +181 -0
- package/dist/orchestrator/src/cli/control/questionReadRetryDeduplication.js +9 -0
- package/dist/orchestrator/src/cli/control/questionReadSequence.js +10 -0
- package/dist/orchestrator/src/cli/control/securityViolationController.js +27 -0
- package/dist/orchestrator/src/cli/control/selectedRunProjection.js +1838 -0
- package/dist/orchestrator/src/cli/control/telegramOversightApiClient.js +48 -0
- package/dist/orchestrator/src/cli/control/telegramOversightBridge.js +180 -0
- package/dist/orchestrator/src/cli/control/telegramOversightBridgeProjectionDeliveryQueue.js +25 -0
- package/dist/orchestrator/src/cli/control/telegramOversightBridgeRuntimeLifecycle.js +45 -0
- package/dist/orchestrator/src/cli/control/telegramOversightBridgeStateStore.js +77 -0
- package/dist/orchestrator/src/cli/control/telegramOversightControlActionApiClient.js +45 -0
- package/dist/orchestrator/src/cli/control/trackerDispatchPilot.js +439 -0
- package/dist/orchestrator/src/cli/control/uiDataController.js +34 -0
- package/dist/orchestrator/src/cli/control/uiSessionController.js +100 -0
- package/dist/orchestrator/src/cli/controlHostCliShell.js +860 -0
- package/dist/orchestrator/src/cli/controlHostFreshnessGaugeCliShell.js +129 -0
- package/dist/orchestrator/src/cli/controlHostSupervisionCliShell.js +2127 -0
- package/dist/orchestrator/src/cli/delegationCliShell.js +62 -0
- package/dist/orchestrator/src/cli/delegationServer.js +567 -678
- package/dist/orchestrator/src/cli/delegationServerCliShell.js +52 -0
- package/dist/orchestrator/src/cli/delegationServerQuestionFlowShell.js +228 -0
- package/dist/orchestrator/src/cli/delegationServerToolDispatchShell.js +411 -0
- package/dist/orchestrator/src/cli/delegationServerTransport.js +274 -0
- package/dist/orchestrator/src/cli/delegationSetup.js +51 -171
- package/dist/orchestrator/src/cli/devtoolsCliShell.js +34 -0
- package/dist/orchestrator/src/cli/doctor.js +542 -122
- package/dist/orchestrator/src/cli/doctorCliRequestShell.js +72 -0
- package/dist/orchestrator/src/cli/doctorCliShell.js +138 -0
- package/dist/orchestrator/src/cli/doctorUsage.js +136 -16
- package/dist/orchestrator/src/cli/exec/experience.js +16 -2
- package/dist/orchestrator/src/cli/exec/summary.js +3 -0
- package/dist/orchestrator/src/cli/execCliShell.js +51 -0
- package/dist/orchestrator/src/cli/flowCliRequestShell.js +44 -0
- package/dist/orchestrator/src/cli/flowCliShell.js +239 -0
- package/dist/orchestrator/src/cli/frontendTestCliRequestShell.js +80 -0
- package/dist/orchestrator/src/cli/frontendTestCliShell.js +41 -0
- package/dist/orchestrator/src/cli/init.js +1 -0
- package/dist/orchestrator/src/cli/initCliShell.js +50 -0
- package/dist/orchestrator/src/cli/linearCliShell.js +1200 -0
- package/dist/orchestrator/src/cli/mcpEnableCliShell.js +132 -0
- package/dist/orchestrator/src/cli/metrics/metricsAggregator.js +3 -2
- package/dist/orchestrator/src/cli/metrics/metricsRecorder.js +56 -0
- package/dist/orchestrator/src/cli/orchestrator.js +66 -1376
- package/dist/orchestrator/src/cli/planCliShell.js +19 -0
- package/dist/orchestrator/src/cli/prCliShell.js +41 -0
- package/dist/orchestrator/src/cli/providerLinearChildLanePhaseContract.js +204 -0
- package/dist/orchestrator/src/cli/providerLinearChildLaneRunner.js +1772 -0
- package/dist/orchestrator/src/cli/providerLinearChildLaneShell.js +2420 -0
- package/dist/orchestrator/src/cli/providerLinearChildStreamShell.js +385 -0
- package/dist/orchestrator/src/cli/providerLinearWorkerRunner.js +5738 -0
- package/dist/orchestrator/src/cli/resumeCliShell.js +14 -0
- package/dist/orchestrator/src/cli/reviewCliLaunchShell.js +72 -0
- package/dist/orchestrator/src/cli/rlm/alignment.js +3 -3
- package/dist/orchestrator/src/cli/rlm/context.js +94 -7
- package/dist/orchestrator/src/cli/rlm/rlmCodexRuntimeShell.js +546 -0
- package/dist/orchestrator/src/cli/rlm/symbolic.js +4 -2
- package/dist/orchestrator/src/cli/rlmCliRequestShell.js +42 -0
- package/dist/orchestrator/src/cli/rlmCompletionCliShell.js +46 -0
- package/dist/orchestrator/src/cli/rlmLaunchCliShell.js +51 -0
- package/dist/orchestrator/src/cli/rlmRunner.js +83 -523
- package/dist/orchestrator/src/cli/run/blockMemory.js +500 -0
- package/dist/orchestrator/src/cli/run/manifest.js +410 -73
- package/dist/orchestrator/src/cli/run/manifestPersister.js +45 -14
- package/dist/orchestrator/src/cli/run/runMemoryController.js +216 -0
- package/dist/orchestrator/src/cli/run/source0.js +690 -0
- package/dist/orchestrator/src/cli/run/workspacePath.js +101 -0
- package/dist/orchestrator/src/cli/runtime/mode.js +2 -1
- package/dist/orchestrator/src/cli/runtime/provider.js +39 -2
- package/dist/orchestrator/src/cli/selfCheckCliShell.js +12 -0
- package/dist/orchestrator/src/cli/services/commandRunner.js +668 -18
- package/dist/orchestrator/src/cli/services/execRuntime.js +66 -1
- package/dist/orchestrator/src/cli/services/orchestratorAutoScoutEvidenceRecorder.js +71 -0
- package/dist/orchestrator/src/cli/services/orchestratorCloudBranchResolution.js +8 -0
- package/dist/orchestrator/src/cli/services/orchestratorCloudEnvironmentResolution.js +22 -0
- package/dist/orchestrator/src/cli/services/orchestratorCloudExecutionLifecycleShell.js +39 -0
- package/dist/orchestrator/src/cli/services/orchestratorCloudPromptBuilder.js +37 -0
- package/dist/orchestrator/src/cli/services/orchestratorCloudRouteFallbackContract.js +45 -0
- package/dist/orchestrator/src/cli/services/orchestratorCloudRouteShell.js +36 -0
- package/dist/orchestrator/src/cli/services/orchestratorCloudTargetExecutor.js +277 -0
- package/dist/orchestrator/src/cli/services/orchestratorControlPlaneLifecycle.js +98 -0
- package/dist/orchestrator/src/cli/services/orchestratorControlPlaneLifecycleShell.js +54 -0
- package/dist/orchestrator/src/cli/services/orchestratorExecutionLifecycle.js +112 -0
- package/dist/orchestrator/src/cli/services/orchestratorExecutionModePolicy.js +27 -0
- package/dist/orchestrator/src/cli/services/orchestratorExecutionRouteAdapterShell.js +59 -0
- package/dist/orchestrator/src/cli/services/orchestratorExecutionRouteDecisionShell.js +57 -0
- package/dist/orchestrator/src/cli/services/orchestratorExecutionRouteState.js +21 -0
- package/dist/orchestrator/src/cli/services/orchestratorExecutionRouter.js +2 -0
- package/dist/orchestrator/src/cli/services/orchestratorLocalPipelineExecutor.js +149 -0
- package/dist/orchestrator/src/cli/services/orchestratorLocalRouteShell.js +63 -0
- package/dist/orchestrator/src/cli/services/orchestratorPlanShell.js +54 -0
- package/dist/orchestrator/src/cli/services/orchestratorPlanTargetTracker.js +16 -0
- package/dist/orchestrator/src/cli/services/orchestratorResumePreparationShell.js +84 -0
- package/dist/orchestrator/src/cli/services/orchestratorResumeTokenValidation.js +15 -0
- package/dist/orchestrator/src/cli/services/orchestratorRunLifecycleCompletion.js +31 -0
- package/dist/orchestrator/src/cli/services/orchestratorRunLifecycleExecutionRegistration.js +37 -0
- package/dist/orchestrator/src/cli/services/orchestratorRunLifecycleOrchestrationShell.js +83 -0
- package/dist/orchestrator/src/cli/services/orchestratorRunLifecycleTaskManagerShell.js +37 -0
- package/dist/orchestrator/src/cli/services/orchestratorRuntimeManifestMutation.js +20 -0
- package/dist/orchestrator/src/cli/services/orchestratorStartPreparationShell.js +56 -0
- package/dist/orchestrator/src/cli/services/orchestratorStatusShell.js +70 -0
- package/dist/orchestrator/src/cli/services/pipelineResolver.js +7 -3
- package/dist/orchestrator/src/cli/services/plannerMemory.js +119 -0
- package/dist/orchestrator/src/cli/services/runPreparation.js +7 -3
- package/dist/orchestrator/src/cli/services/runSummaryWriter.js +9 -0
- package/dist/orchestrator/src/cli/setupBootstrapShell.js +114 -0
- package/dist/orchestrator/src/cli/setupCliShell.js +51 -0
- package/dist/orchestrator/src/cli/skillsCliShell.js +56 -0
- package/dist/orchestrator/src/cli/startCliRequestShell.js +53 -0
- package/dist/orchestrator/src/cli/startCliShell.js +68 -0
- package/dist/orchestrator/src/cli/statusCliShell.js +22 -0
- package/dist/orchestrator/src/cli/utils/authProvenanceFingerprint.js +27 -0
- package/dist/orchestrator/src/cli/utils/cloudPreflight.js +83 -1
- package/dist/orchestrator/src/cli/utils/delegationConfigParser.js +250 -0
- package/dist/orchestrator/src/cli/utils/delegationMcpHealth.js +1382 -0
- package/dist/orchestrator/src/cli/utils/devtools.js +2 -54
- package/dist/orchestrator/src/cli/utils/mcpServerEntry.js +53 -0
- package/dist/orchestrator/src/cli/utils/packageProgramResolver.js +151 -0
- package/dist/orchestrator/src/cli/utils/providerOverrideEnv.js +71 -0
- package/dist/orchestrator/src/cli/utils/trailingJsonObject.js +59 -0
- package/dist/orchestrator/src/learning/crystalizer.js +2 -2
- package/dist/orchestrator/src/persistence/ExperienceStore.js +233 -49
- package/dist/orchestrator/src/persistence/TaskStateStore.js +6 -6
- package/dist/orchestrator/src/persistence/lockFile.js +70 -4
- package/dist/orchestrator/src/persistence/sanitizeIdentifier.js +39 -0
- package/dist/orchestrator/src/sync/createCloudSyncWorker.js +3 -2
- package/dist/orchestrator/src/utils/atomicWrite.js +17 -2
- package/dist/packages/orchestrator/src/exec/unified-exec.js +99 -6
- package/dist/packages/orchestrator/src/instructions/promptPacks.js +150 -19
- package/dist/packages/sdk-node/src/orchestrator.js +137 -13
- package/dist/packages/shared/config/designConfig.js +8 -1
- package/dist/packages/shared/streams/stdio.js +1 -1
- package/dist/scripts/design/pipeline/permit.js +15 -0
- package/dist/scripts/lib/docs-catalog.js +365 -0
- package/dist/scripts/lib/docs-helpers.js +87 -5
- package/dist/scripts/lib/pr-watch-merge.js +1088 -80
- package/dist/scripts/lib/provider-run-contract.js +26 -0
- package/dist/scripts/lib/review-command-intent-classification.js +532 -0
- package/dist/scripts/lib/review-command-probe-classification.js +385 -0
- package/dist/scripts/lib/review-execution-boundary-preflight.js +279 -0
- package/dist/scripts/lib/review-execution-runtime.js +753 -0
- package/dist/scripts/lib/review-execution-state.js +1144 -0
- package/dist/scripts/lib/review-execution-telemetry.js +215 -0
- package/dist/scripts/lib/review-inspection-target-parsing.js +78 -0
- package/dist/scripts/lib/review-launch-attempt.js +601 -0
- package/dist/scripts/lib/review-meta-surface-boundary-analysis.js +300 -0
- package/dist/scripts/lib/review-meta-surface-normalization.js +746 -0
- package/dist/scripts/lib/review-non-interactive-handoff.js +61 -0
- package/dist/scripts/lib/review-prompt-context.js +376 -0
- package/dist/scripts/lib/review-scope-advisory.js +286 -0
- package/dist/scripts/lib/review-scope-paths.js +123 -0
- package/dist/scripts/lib/review-shell-command-parser.js +389 -0
- package/dist/scripts/lib/review-shell-env-interpreter.js +340 -0
- package/dist/scripts/lib/run-manifests.js +192 -36
- package/dist/scripts/lib/spark-policy-classifier.js +593 -0
- package/dist/scripts/run-review.js +507 -1777
- package/docs/public/downstream-setup.md +106 -0
- package/docs/public/provider-onboarding.md +173 -0
- package/package.json +30 -11
- package/plugins/codex-orchestrator/.codex-plugin/plugin.json +30 -0
- package/plugins/codex-orchestrator/.mcp.json +13 -0
- package/plugins/codex-orchestrator/launcher.mjs +359 -0
- package/schemas/manifest.json +395 -0
- package/skills/chrome-devtools/SKILL.md +1 -1
- package/skills/codex-orchestrator/SKILL.md +83 -0
- package/skills/collab-subagents-first/SKILL.md +2 -1
- package/skills/delegation-usage/DELEGATION_GUIDE.md +24 -11
- package/skills/delegation-usage/SKILL.md +20 -13
- package/skills/land/SKILL.md +77 -0
- package/skills/linear/SKILL.md +255 -0
- package/skills/release/SKILL.md +47 -3
- package/skills/standalone-review/SKILL.md +6 -1
- package/templates/README.md +4 -2
- package/templates/codex/.codex/agents/awaiter-high.toml +2 -2
- package/templates/codex/.codex/agents/explorer-fast.toml +1 -0
- package/templates/codex/.codex/agents/worker-complex.toml +1 -1
- package/templates/codex/.codex/config.toml +3 -4
- package/templates/codex/.codex/providers/README.md +13 -0
- package/templates/codex/.codex/providers/control.example.json +18 -0
- package/templates/codex/.codex/providers/provider.env.example +15 -0
- package/templates/codex/AGENTS.md +12 -7
- package/templates/codex/mcp-client.json +5 -1
- package/docs/README.md +0 -307
- package/docs/assets/setup.gif +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { randomBytes } from 'node:crypto';
|
|
2
2
|
import { createReadStream } from 'node:fs';
|
|
3
|
-
import { appendFile, mkdir, open
|
|
3
|
+
import { appendFile, mkdir, open } from 'node:fs/promises';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import { createInterface } from 'node:readline';
|
|
6
6
|
import { listDirectories, resolveEnvironmentPaths } from '../../../scripts/lib/run-manifests.js';
|
|
@@ -49,7 +49,7 @@ export class ExperienceStore {
|
|
|
49
49
|
}
|
|
50
50
|
const taskId = sanitizeTaskId(inputs[0].taskId);
|
|
51
51
|
const lockPath = this.buildLockPath(taskId);
|
|
52
|
-
await this.acquireLock(taskId, lockPath);
|
|
52
|
+
const lock = await this.acquireLock(taskId, lockPath);
|
|
53
53
|
try {
|
|
54
54
|
const targetDir = join(this.outDir, taskId);
|
|
55
55
|
await mkdir(targetDir, { recursive: true });
|
|
@@ -61,20 +61,34 @@ export class ExperienceStore {
|
|
|
61
61
|
return nextRecords;
|
|
62
62
|
}
|
|
63
63
|
finally {
|
|
64
|
-
await this.releaseLock(
|
|
64
|
+
await this.releaseLock(lock);
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
67
|
async fetchTop(params) {
|
|
68
|
+
const result = await this.selectTop({
|
|
69
|
+
domain: params.domain,
|
|
70
|
+
limit: params.limit,
|
|
71
|
+
minScore: params.minReward,
|
|
72
|
+
taskId: params.taskId
|
|
73
|
+
});
|
|
74
|
+
return result.records;
|
|
75
|
+
}
|
|
76
|
+
async selectTop(params) {
|
|
68
77
|
const safeDomain = params.domain.trim();
|
|
78
|
+
const policy = resolveExperienceSelectionPolicy(params.policy, params.minScore);
|
|
69
79
|
if (!safeDomain) {
|
|
70
|
-
return
|
|
80
|
+
return emptySelection(policy);
|
|
71
81
|
}
|
|
72
82
|
const taskFilter = params.taskId ? sanitizeTaskId(params.taskId) : null;
|
|
73
83
|
const limit = Math.max(0, params.limit);
|
|
74
84
|
if (limit === 0) {
|
|
75
|
-
return
|
|
85
|
+
return emptySelection(policy);
|
|
76
86
|
}
|
|
77
|
-
const
|
|
87
|
+
const entries = [];
|
|
88
|
+
const selectedRecordEntriesBySource = new Map();
|
|
89
|
+
const selectedRecordLookup = new Map();
|
|
90
|
+
const sourceCandidateCounts = new Map();
|
|
91
|
+
let candidateCount = 0;
|
|
78
92
|
const applyRecord = (record) => {
|
|
79
93
|
if (record.domain !== safeDomain) {
|
|
80
94
|
return;
|
|
@@ -82,7 +96,17 @@ export class ExperienceStore {
|
|
|
82
96
|
if (taskFilter && record.taskId !== taskFilter) {
|
|
83
97
|
return;
|
|
84
98
|
}
|
|
85
|
-
|
|
99
|
+
const entry = createSelectionEntry(record, policy);
|
|
100
|
+
entries.push(entry);
|
|
101
|
+
candidateCount += 1;
|
|
102
|
+
sourceCandidateCounts.set(entry.sourceBucketKey, (sourceCandidateCounts.get(entry.sourceBucketKey) ?? 0) + 1);
|
|
103
|
+
// Keep full diagnostics for every candidate while retaining only bounded records for materialization.
|
|
104
|
+
const retainedEntry = { ...entry, record };
|
|
105
|
+
selectedRecordLookup.set(retainedEntry.id, record);
|
|
106
|
+
const prunedEntry = insertSourceCandidate(selectedRecordEntriesBySource, retainedEntry, limit);
|
|
107
|
+
if (prunedEntry?.record) {
|
|
108
|
+
selectedRecordLookup.delete(prunedEntry.id);
|
|
109
|
+
}
|
|
86
110
|
};
|
|
87
111
|
if (taskFilter) {
|
|
88
112
|
await this.scanRecords(join(this.outDir, taskFilter, 'experiences.jsonl'), applyRecord);
|
|
@@ -93,7 +117,7 @@ export class ExperienceStore {
|
|
|
93
117
|
await this.scanRecords(join(this.outDir, dir, 'experiences.jsonl'), applyRecord);
|
|
94
118
|
}
|
|
95
119
|
}
|
|
96
|
-
return
|
|
120
|
+
return selectCompetitiveTop(entries, limit, policy, candidateCount, sourceCandidateCounts, selectedRecordLookup);
|
|
97
121
|
}
|
|
98
122
|
verifyStamp(record) {
|
|
99
123
|
return HEX_STAMP_PATTERN.test(record.stampSignature);
|
|
@@ -135,7 +159,7 @@ export class ExperienceStore {
|
|
|
135
159
|
return join(this.runsDir, `${taskId}.experiences.lock`);
|
|
136
160
|
}
|
|
137
161
|
async acquireLock(taskId, lockPath) {
|
|
138
|
-
await acquireLockWithRetry({
|
|
162
|
+
return await acquireLockWithRetry({
|
|
139
163
|
taskId,
|
|
140
164
|
lockPath,
|
|
141
165
|
retry: this.lockRetry,
|
|
@@ -145,8 +169,8 @@ export class ExperienceStore {
|
|
|
145
169
|
createError: (id, attempts) => new ExperienceStoreLockError(`Failed to acquire experience lock for ${id} after ${attempts} attempts`, id)
|
|
146
170
|
});
|
|
147
171
|
}
|
|
148
|
-
async releaseLock(
|
|
149
|
-
await
|
|
172
|
+
async releaseLock(lock) {
|
|
173
|
+
await lock.release();
|
|
150
174
|
}
|
|
151
175
|
async scanRecords(filePath, onRecord) {
|
|
152
176
|
try {
|
|
@@ -205,52 +229,212 @@ export class ExperienceStore {
|
|
|
205
229
|
return `exp-${Date.now().toString(36)}-${suffix}`;
|
|
206
230
|
}
|
|
207
231
|
}
|
|
208
|
-
function
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
232
|
+
function emptySelection(policy) {
|
|
233
|
+
return {
|
|
234
|
+
records: [],
|
|
235
|
+
diagnostics: {
|
|
236
|
+
policy,
|
|
237
|
+
candidate_count: 0,
|
|
238
|
+
selected_count: 0,
|
|
239
|
+
selected_ids: [],
|
|
240
|
+
suppressed_source_keys: [],
|
|
241
|
+
selected: [],
|
|
242
|
+
candidates: []
|
|
214
243
|
}
|
|
215
|
-
const aTime = a.record.createdAt ?? '';
|
|
216
|
-
const bTime = b.record.createdAt ?? '';
|
|
217
|
-
return aTime.localeCompare(bTime);
|
|
218
244
|
};
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
245
|
+
}
|
|
246
|
+
function resolveExperienceSelectionPolicy(policy, minScoreFallback) {
|
|
247
|
+
return {
|
|
248
|
+
kind: 'competitive_scoring_v1',
|
|
249
|
+
minScore: normalizeOptionalScore(policy?.minScore ?? minScoreFallback, 'selection.policy.minScore', null),
|
|
250
|
+
scoreWeights: {
|
|
251
|
+
gtScore: normalizeNonNegativeScore(policy?.scoreWeights?.gtScore, 'selection.policy.scoreWeights.gtScore', 1),
|
|
252
|
+
relativeRank: normalizeNonNegativeScore(policy?.scoreWeights?.relativeRank, 'selection.policy.scoreWeights.relativeRank', 1)
|
|
253
|
+
},
|
|
254
|
+
antiDominanceNormalization: {
|
|
255
|
+
enabled: normalizeBoolean(policy?.antiDominanceNormalization?.enabled, 'selection.policy.antiDominanceNormalization.enabled', true),
|
|
256
|
+
strength: normalizeNonNegativeScore(policy?.antiDominanceNormalization?.strength, 'selection.policy.antiDominanceNormalization.strength', 0.5),
|
|
257
|
+
sourceGrouping: 'provenance_fallback_v1'
|
|
223
258
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
function createSelectionEntry(record, policy) {
|
|
262
|
+
const source = deriveExperienceSource(record);
|
|
263
|
+
const rawScore = roundScore(record.reward.gtScore * policy.scoreWeights.gtScore +
|
|
264
|
+
record.reward.relativeRank * policy.scoreWeights.relativeRank);
|
|
265
|
+
return {
|
|
266
|
+
id: record.id,
|
|
267
|
+
createdAt: record.createdAt,
|
|
268
|
+
sourceKey: source.key,
|
|
269
|
+
sourceKind: source.kind,
|
|
270
|
+
sourceBucketKey: formatExperienceSourceBucketKey(source.kind, source.key),
|
|
271
|
+
rawScore,
|
|
272
|
+
competitiveScore: rawScore,
|
|
273
|
+
dominancePenalty: 0,
|
|
274
|
+
selected: false,
|
|
275
|
+
selectedSlot: null,
|
|
276
|
+
exclusionReason: policy.minScore !== null && rawScore < policy.minScore ? 'raw_score_below_min_score' : null
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
function insertSourceCandidate(entriesBySource, entry, limit) {
|
|
280
|
+
const bucket = entriesBySource.get(entry.sourceBucketKey) ?? [];
|
|
281
|
+
let insertAt = 0;
|
|
282
|
+
while (insertAt < bucket.length &&
|
|
283
|
+
compareSelectionEntriesByRaw(bucket[insertAt], entry) <= 0) {
|
|
284
|
+
insertAt += 1;
|
|
285
|
+
}
|
|
286
|
+
bucket.splice(insertAt, 0, entry);
|
|
287
|
+
let prunedEntry = null;
|
|
288
|
+
if (bucket.length > limit) {
|
|
289
|
+
prunedEntry = bucket.pop() ?? null;
|
|
290
|
+
}
|
|
291
|
+
entriesBySource.set(entry.sourceBucketKey, bucket);
|
|
292
|
+
return prunedEntry;
|
|
293
|
+
}
|
|
294
|
+
function selectCompetitiveTop(entries, limit, policy, candidateCount = entries.length, sourceCandidateCounts = new Map(), selectedRecordLookup = new Map()) {
|
|
295
|
+
const remaining = entries.filter((entry) => entry.exclusionReason === null);
|
|
296
|
+
const selectedCounts = new Map();
|
|
297
|
+
const selectedEntries = [];
|
|
298
|
+
for (let slot = 1; slot <= limit && remaining.length > 0; slot += 1) {
|
|
299
|
+
for (const entry of remaining) {
|
|
300
|
+
const priorSelections = selectedCounts.get(entry.sourceBucketKey) ?? 0;
|
|
301
|
+
const dominancePenalty = policy.antiDominanceNormalization.enabled
|
|
302
|
+
? roundScore(policy.antiDominanceNormalization.strength * priorSelections)
|
|
303
|
+
: 0;
|
|
304
|
+
entry.dominancePenalty = dominancePenalty;
|
|
305
|
+
entry.competitiveScore = roundScore(entry.rawScore - dominancePenalty);
|
|
228
306
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
307
|
+
remaining.sort(compareSelectionEntries);
|
|
308
|
+
const best = remaining[0];
|
|
309
|
+
if (!best || (policy.minScore !== null && best.competitiveScore < policy.minScore)) {
|
|
310
|
+
for (const entry of remaining) {
|
|
311
|
+
entry.exclusionReason = 'competitive_score_below_min_score';
|
|
312
|
+
}
|
|
313
|
+
break;
|
|
232
314
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
315
|
+
best.selected = true;
|
|
316
|
+
best.selectedSlot = slot;
|
|
317
|
+
best.exclusionReason = null;
|
|
318
|
+
selectedEntries.push(best);
|
|
319
|
+
selectedCounts.set(best.sourceBucketKey, (selectedCounts.get(best.sourceBucketKey) ?? 0) + 1);
|
|
320
|
+
remaining.shift();
|
|
321
|
+
}
|
|
322
|
+
for (const entry of remaining) {
|
|
323
|
+
if (entry.exclusionReason === null) {
|
|
324
|
+
entry.exclusionReason = 'outcompeted';
|
|
236
325
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
326
|
+
}
|
|
327
|
+
if (sourceCandidateCounts.size === 0) {
|
|
328
|
+
for (const entry of entries) {
|
|
329
|
+
sourceCandidateCounts.set(entry.sourceBucketKey, (sourceCandidateCounts.get(entry.sourceBucketKey) ?? 0) + 1);
|
|
240
330
|
}
|
|
241
|
-
}
|
|
242
|
-
const
|
|
243
|
-
.
|
|
244
|
-
.
|
|
245
|
-
|
|
246
|
-
|
|
331
|
+
}
|
|
332
|
+
const suppressedSourceKeys = [...sourceCandidateCounts.entries()]
|
|
333
|
+
.filter(([sourceBucketKey, count]) => count > (selectedCounts.get(sourceBucketKey) ?? 0))
|
|
334
|
+
.map(([sourceBucketKey]) => sourceBucketKey)
|
|
335
|
+
.sort((a, b) => a.localeCompare(b));
|
|
336
|
+
return {
|
|
337
|
+
records: selectedEntries.map((entry) => {
|
|
338
|
+
const record = entry.record ?? selectedRecordLookup.get(entry.id);
|
|
339
|
+
if (!record) {
|
|
340
|
+
throw new Error(`Selected experience ${entry.id} is missing a retained record`);
|
|
341
|
+
}
|
|
342
|
+
return record;
|
|
343
|
+
}),
|
|
344
|
+
diagnostics: {
|
|
345
|
+
policy,
|
|
346
|
+
candidate_count: candidateCount,
|
|
347
|
+
selected_count: selectedEntries.length,
|
|
348
|
+
selected_ids: selectedEntries.map((entry) => entry.id),
|
|
349
|
+
suppressed_source_keys: suppressedSourceKeys,
|
|
350
|
+
selected: selectedEntries.map((entry) => ({
|
|
351
|
+
id: entry.id,
|
|
352
|
+
source_key: entry.sourceKey,
|
|
353
|
+
source_kind: entry.sourceKind,
|
|
354
|
+
raw_score: entry.rawScore,
|
|
355
|
+
competitive_score: entry.competitiveScore,
|
|
356
|
+
dominance_penalty: entry.dominancePenalty
|
|
357
|
+
})),
|
|
358
|
+
candidates: entries
|
|
359
|
+
.slice()
|
|
360
|
+
.sort((a, b) => compareSelectionEntries(a, b))
|
|
361
|
+
.map((entry) => ({
|
|
362
|
+
id: entry.id,
|
|
363
|
+
source_key: entry.sourceKey,
|
|
364
|
+
source_kind: entry.sourceKind,
|
|
365
|
+
raw_score: entry.rawScore,
|
|
366
|
+
competitive_score: entry.competitiveScore,
|
|
367
|
+
dominance_penalty: entry.dominancePenalty,
|
|
368
|
+
selected: entry.selected,
|
|
369
|
+
selected_slot: entry.selectedSlot,
|
|
370
|
+
exclusion_reason: entry.exclusionReason
|
|
371
|
+
}))
|
|
247
372
|
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
.
|
|
253
|
-
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
function deriveExperienceSource(record) {
|
|
376
|
+
if (record.groupId && record.groupId.trim()) {
|
|
377
|
+
return { kind: 'group_id', key: record.groupId.trim() };
|
|
378
|
+
}
|
|
379
|
+
if (record.runId && record.runId.trim()) {
|
|
380
|
+
return { kind: 'run_id', key: record.runId.trim() };
|
|
381
|
+
}
|
|
382
|
+
if (record.manifestPath && record.manifestPath.trim()) {
|
|
383
|
+
return { kind: 'manifest_path', key: record.manifestPath.trim() };
|
|
384
|
+
}
|
|
385
|
+
return { kind: 'stamp_signature', key: record.stampSignature };
|
|
386
|
+
}
|
|
387
|
+
function compareSelectionEntries(a, b) {
|
|
388
|
+
if (a.competitiveScore !== b.competitiveScore) {
|
|
389
|
+
return b.competitiveScore - a.competitiveScore;
|
|
390
|
+
}
|
|
391
|
+
return compareSelectionEntriesByRaw(a, b);
|
|
392
|
+
}
|
|
393
|
+
function compareSelectionEntriesByRaw(a, b) {
|
|
394
|
+
if (a.rawScore !== b.rawScore) {
|
|
395
|
+
return b.rawScore - a.rawScore;
|
|
396
|
+
}
|
|
397
|
+
const aTime = a.createdAt ?? '';
|
|
398
|
+
const bTime = b.createdAt ?? '';
|
|
399
|
+
const timeCompare = bTime.localeCompare(aTime);
|
|
400
|
+
if (timeCompare !== 0) {
|
|
401
|
+
return timeCompare;
|
|
402
|
+
}
|
|
403
|
+
return a.id.localeCompare(b.id);
|
|
404
|
+
}
|
|
405
|
+
function formatExperienceSourceBucketKey(kind, key) {
|
|
406
|
+
return `${kind}:${key}`;
|
|
407
|
+
}
|
|
408
|
+
function roundScore(value) {
|
|
409
|
+
if (!Number.isFinite(value)) {
|
|
410
|
+
return 0;
|
|
411
|
+
}
|
|
412
|
+
return Math.round(value * 1_000_000) / 1_000_000;
|
|
413
|
+
}
|
|
414
|
+
function normalizeOptionalScore(value, field, defaultValue) {
|
|
415
|
+
if (value === null || value === undefined) {
|
|
416
|
+
return defaultValue;
|
|
417
|
+
}
|
|
418
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
419
|
+
throw new Error(`${field} must be a finite non-negative number.`);
|
|
420
|
+
}
|
|
421
|
+
return value;
|
|
422
|
+
}
|
|
423
|
+
function normalizeNonNegativeScore(value, field, defaultValue) {
|
|
424
|
+
const normalized = normalizeOptionalScore(value, field, defaultValue);
|
|
425
|
+
if (normalized === null) {
|
|
426
|
+
throw new Error(`${field} must be a finite non-negative number.`);
|
|
427
|
+
}
|
|
428
|
+
return normalized;
|
|
429
|
+
}
|
|
430
|
+
function normalizeBoolean(value, field, defaultValue) {
|
|
431
|
+
if (value === undefined) {
|
|
432
|
+
return defaultValue;
|
|
433
|
+
}
|
|
434
|
+
if (typeof value !== 'boolean') {
|
|
435
|
+
throw new Error(`${field} must be a boolean.`);
|
|
436
|
+
}
|
|
437
|
+
return value;
|
|
254
438
|
}
|
|
255
439
|
function truncateSummary(value, maxWords) {
|
|
256
440
|
const tokens = value.trim().split(/\s+/u).filter(Boolean);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mkdir, readFile
|
|
1
|
+
import { mkdir, readFile } from 'node:fs/promises';
|
|
2
2
|
import { dirname, join } from 'node:path';
|
|
3
3
|
import { acquireLockWithRetry } from './lockFile.js';
|
|
4
4
|
import { sanitizeTaskId } from './sanitizeTaskId.js';
|
|
@@ -37,7 +37,7 @@ export class TaskStateStore {
|
|
|
37
37
|
async recordRun(summary) {
|
|
38
38
|
const safeTaskId = sanitizeTaskId(summary.taskId);
|
|
39
39
|
const lockPath = this.buildLockPath(safeTaskId);
|
|
40
|
-
await this.acquireLock(safeTaskId, lockPath);
|
|
40
|
+
const lock = await this.acquireLock(safeTaskId, lockPath);
|
|
41
41
|
try {
|
|
42
42
|
await this.ensureDirectory(this.outDir);
|
|
43
43
|
const taskOutDir = join(this.outDir, safeTaskId);
|
|
@@ -48,7 +48,7 @@ export class TaskStateStore {
|
|
|
48
48
|
await writeAtomicFile(snapshotPath, JSON.stringify(updated, null, 2));
|
|
49
49
|
}
|
|
50
50
|
finally {
|
|
51
|
-
await this.releaseLock(
|
|
51
|
+
await this.releaseLock(lock);
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
buildLockPath(taskId) {
|
|
@@ -56,7 +56,7 @@ export class TaskStateStore {
|
|
|
56
56
|
return join(this.runsDir, `${safeTaskId}.lock`);
|
|
57
57
|
}
|
|
58
58
|
async acquireLock(taskId, lockPath) {
|
|
59
|
-
await acquireLockWithRetry({
|
|
59
|
+
return await acquireLockWithRetry({
|
|
60
60
|
taskId,
|
|
61
61
|
lockPath,
|
|
62
62
|
retry: this.lockRetry,
|
|
@@ -64,8 +64,8 @@ export class TaskStateStore {
|
|
|
64
64
|
createError: (id, attempts) => new TaskStateStoreLockError(`Failed to acquire task state lock for ${id} after ${attempts} attempts`, id)
|
|
65
65
|
});
|
|
66
66
|
}
|
|
67
|
-
async releaseLock(
|
|
68
|
-
await
|
|
67
|
+
async releaseLock(lock) {
|
|
68
|
+
await lock.release();
|
|
69
69
|
}
|
|
70
70
|
async ensureDirectory(path) {
|
|
71
71
|
await mkdir(path, { recursive: true });
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { open, readFile, rm, stat } from 'node:fs/promises';
|
|
2
3
|
import { setTimeout as delay } from 'node:timers/promises';
|
|
3
4
|
export async function acquireLockWithRetry(params) {
|
|
4
5
|
await params.ensureDirectory();
|
|
@@ -9,9 +10,17 @@ export async function acquireLockWithRetry(params) {
|
|
|
9
10
|
while (attempt < maxAttempts) {
|
|
10
11
|
attempt += 1;
|
|
11
12
|
try {
|
|
12
|
-
const
|
|
13
|
-
await
|
|
14
|
-
|
|
13
|
+
const ownerToken = randomUUID();
|
|
14
|
+
const handle = await open(params.lockPath, 'wx+');
|
|
15
|
+
try {
|
|
16
|
+
await handle.writeFile(ownerToken, 'utf8');
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
await handle.close();
|
|
20
|
+
await rm(params.lockPath, { force: true });
|
|
21
|
+
throw error;
|
|
22
|
+
}
|
|
23
|
+
return createAcquiredLock(params.lockPath, ownerToken, handle, staleMs);
|
|
15
24
|
}
|
|
16
25
|
catch (error) {
|
|
17
26
|
if (error.code !== 'EEXIST') {
|
|
@@ -31,6 +40,63 @@ export async function acquireLockWithRetry(params) {
|
|
|
31
40
|
delayMs = Math.min(delayMs * backoffFactor, maxDelayMs);
|
|
32
41
|
}
|
|
33
42
|
}
|
|
43
|
+
throw params.createError(params.taskId, attempt);
|
|
44
|
+
}
|
|
45
|
+
function createAcquiredLock(lockPath, ownerToken, handle, staleMs) {
|
|
46
|
+
let released = false;
|
|
47
|
+
const keepalive = createLockKeepalive(handle, staleMs);
|
|
48
|
+
return {
|
|
49
|
+
lockPath,
|
|
50
|
+
ownerToken,
|
|
51
|
+
async release() {
|
|
52
|
+
if (released) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
released = true;
|
|
56
|
+
keepalive.stop();
|
|
57
|
+
await handle.close();
|
|
58
|
+
await releaseLockIfOwned(lockPath, ownerToken);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function createLockKeepalive(handle, staleMs) {
|
|
63
|
+
if (staleMs <= 0) {
|
|
64
|
+
return { stop() { } };
|
|
65
|
+
}
|
|
66
|
+
const intervalMs = Math.max(10, Math.floor(staleMs / 2));
|
|
67
|
+
const timer = setInterval(() => {
|
|
68
|
+
void touchLockHandle(handle);
|
|
69
|
+
}, intervalMs);
|
|
70
|
+
timer.unref?.();
|
|
71
|
+
return {
|
|
72
|
+
stop() {
|
|
73
|
+
clearInterval(timer);
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
async function touchLockHandle(handle) {
|
|
78
|
+
try {
|
|
79
|
+
const now = new Date();
|
|
80
|
+
await handle.utimes(now, now);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
// Ignore keepalive failures; acquisition/release paths still surface real errors.
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
async function releaseLockIfOwned(lockPath, ownerToken) {
|
|
87
|
+
try {
|
|
88
|
+
const currentOwner = (await readFile(lockPath, 'utf8')).trim();
|
|
89
|
+
if (currentOwner !== ownerToken) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
await rm(lockPath, { force: true });
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
if (error.code === 'ENOENT') {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
throw error;
|
|
99
|
+
}
|
|
34
100
|
}
|
|
35
101
|
async function clearStaleLock(lockPath, staleMs) {
|
|
36
102
|
try {
|
|
@@ -1,4 +1,37 @@
|
|
|
1
1
|
const WINDOWS_FORBIDDEN_CHARACTERS = new Set(['<', '>', ':', '"', '|', '?', '*']);
|
|
2
|
+
const WINDOWS_RESERVED_DEVICE_NAMES = new Set([
|
|
3
|
+
'CON',
|
|
4
|
+
'PRN',
|
|
5
|
+
'AUX',
|
|
6
|
+
'NUL',
|
|
7
|
+
'COM1',
|
|
8
|
+
'COM2',
|
|
9
|
+
'COM3',
|
|
10
|
+
'COM4',
|
|
11
|
+
'COM5',
|
|
12
|
+
'COM6',
|
|
13
|
+
'COM7',
|
|
14
|
+
'COM8',
|
|
15
|
+
'COM9',
|
|
16
|
+
'LPT1',
|
|
17
|
+
'LPT2',
|
|
18
|
+
'LPT3',
|
|
19
|
+
'LPT4',
|
|
20
|
+
'LPT5',
|
|
21
|
+
'LPT6',
|
|
22
|
+
'LPT7',
|
|
23
|
+
'LPT8',
|
|
24
|
+
'LPT9',
|
|
25
|
+
'CONIN$',
|
|
26
|
+
'CONOUT$'
|
|
27
|
+
]);
|
|
28
|
+
function isWindowsReservedDeviceName(value) {
|
|
29
|
+
const baseName = value.split('.')[0]?.trimEnd();
|
|
30
|
+
if (!baseName) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
return WINDOWS_RESERVED_DEVICE_NAMES.has(baseName.toUpperCase());
|
|
34
|
+
}
|
|
2
35
|
export function sanitizeIdentifier(kind, value) {
|
|
3
36
|
const label = kind === 'task' ? 'task' : 'run';
|
|
4
37
|
if (!value) {
|
|
@@ -22,5 +55,11 @@ export function sanitizeIdentifier(kind, value) {
|
|
|
22
55
|
if (value.includes('/') || value.includes('\\')) {
|
|
23
56
|
throw new Error(`Invalid ${label} ID "${value}": slashes are not allowed.`);
|
|
24
57
|
}
|
|
58
|
+
if (value.endsWith('.') || value.endsWith(' ')) {
|
|
59
|
+
throw new Error(`Invalid ${label} ID "${value}": trailing dots or spaces are not allowed.`);
|
|
60
|
+
}
|
|
61
|
+
if (isWindowsReservedDeviceName(value)) {
|
|
62
|
+
throw new Error(`Invalid ${label} ID "${value}": Windows reserved device names are not allowed.`);
|
|
63
|
+
}
|
|
25
64
|
return value;
|
|
26
65
|
}
|
|
@@ -2,8 +2,9 @@ import { CloudRunsHttpClient } from './CloudRunsHttpClient.js';
|
|
|
2
2
|
import { CloudSyncWorker } from './CloudSyncWorker.js';
|
|
3
3
|
/**
|
|
4
4
|
* Convenience factory that wires the CloudRuns HTTP client to the CloudSync worker using
|
|
5
|
-
* the credential broker defined in the architecture spec.
|
|
6
|
-
*
|
|
5
|
+
* the credential broker defined in the architecture spec. This remains opt-in integration
|
|
6
|
+
* glue: the default CLI does not construct or start a CloudSyncWorker unless a caller wires
|
|
7
|
+
* it explicitly. Callers can override client or worker options as needed.
|
|
7
8
|
*/
|
|
8
9
|
export function createCloudSyncWorker(params) {
|
|
9
10
|
const client = new CloudRunsHttpClient({
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mkdir, rename, writeFile } from 'node:fs/promises';
|
|
1
|
+
import { chmod, mkdir, rename, stat, writeFile } from 'node:fs/promises';
|
|
2
2
|
import { dirname } from 'node:path';
|
|
3
3
|
let atomicWriteCounter = 0;
|
|
4
4
|
export function buildAtomicTempPath(destination, now = Date.now, pid = process.pid) {
|
|
@@ -10,6 +10,21 @@ export async function writeAtomicFile(destination, contents, options = {}) {
|
|
|
10
10
|
if (options.ensureDir) {
|
|
11
11
|
await mkdir(dirname(destination), { recursive: true });
|
|
12
12
|
}
|
|
13
|
-
|
|
13
|
+
let existingMode = null;
|
|
14
|
+
try {
|
|
15
|
+
existingMode = (await stat(destination)).mode & 0o777;
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
if (error.code !== 'ENOENT') {
|
|
19
|
+
throw error;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
await writeFile(tmpPath, contents, {
|
|
23
|
+
encoding: options.encoding ?? 'utf8',
|
|
24
|
+
mode: existingMode ?? undefined
|
|
25
|
+
});
|
|
26
|
+
if (existingMode !== null) {
|
|
27
|
+
await chmod(tmpPath, existingMode);
|
|
28
|
+
}
|
|
14
29
|
await rename(tmpPath, destination);
|
|
15
30
|
}
|