@openlife/cli 1.7.3
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/INSTALL.md +266 -0
- package/LICENSE +21 -0
- package/README.md +142 -0
- package/bin/openlife.js +3 -0
- package/dist/admin_panel_server.js +66 -0
- package/dist/cli/AgentManager.js +109 -0
- package/dist/cli/AutonomousInstaller.js +134 -0
- package/dist/cli/DreamOrganizer.js +88 -0
- package/dist/cli/HostInstaller.js +426 -0
- package/dist/cli/InstallBanner.js +16 -0
- package/dist/cli/InstallFlow.js +256 -0
- package/dist/cli/InstallHeadless.js +47 -0
- package/dist/cli/InstallModules.js +148 -0
- package/dist/cli/InstallStateStore.js +75 -0
- package/dist/cli/InstallWizard.js +364 -0
- package/dist/cli/ProfileManager.js +163 -0
- package/dist/cli/SystemInstaller.js +89 -0
- package/dist/cli/WorldClassCommands.js +208 -0
- package/dist/design/DesignMdImporter.js +82 -0
- package/dist/design/DesignMdMode.js +93 -0
- package/dist/design/DesignMdRegistry.js +67 -0
- package/dist/index.js +2575 -0
- package/dist/memory/ConversationMemory.js +33 -0
- package/dist/memory/LocalMemoryProvider.js +86 -0
- package/dist/memory/Mem0Provider.js +16 -0
- package/dist/memory/MemoryNamespacePolicy.js +27 -0
- package/dist/memory/MemoryOrchestrator.js +65 -0
- package/dist/memory/MemoryPromotionFlow.js +32 -0
- package/dist/memory/MemoryProvider.js +2 -0
- package/dist/memory/MemoryProviderRegistry.js +27 -0
- package/dist/memory/MemoryRetentionPolicy.js +60 -0
- package/dist/memory/MempalaceProvider.js +72 -0
- package/dist/memory/OmniMemory.js +106 -0
- package/dist/memory/RedisAgentMemoryProvider.js +16 -0
- package/dist/memory/SessionManager.js +86 -0
- package/dist/memory/ZepGraphitiProvider.js +16 -0
- package/dist/orchestrator/AgentRegistry.js +56 -0
- package/dist/orchestrator/AgentScoring.js +82 -0
- package/dist/orchestrator/AgentTeam.js +22 -0
- package/dist/orchestrator/ArbitrationAgent.js +43 -0
- package/dist/orchestrator/ArbitrationScorecard.js +17 -0
- package/dist/orchestrator/AssetPromotionEngine.js +65 -0
- package/dist/orchestrator/AssetReuseRouter.js +63 -0
- package/dist/orchestrator/BenchmarkEngine.js +75 -0
- package/dist/orchestrator/Brain.js +298 -0
- package/dist/orchestrator/CadenceEngine.js +76 -0
- package/dist/orchestrator/CapabilityRouter.js +36 -0
- package/dist/orchestrator/CommandLanguage.js +27 -0
- package/dist/orchestrator/CommandRouter.js +70 -0
- package/dist/orchestrator/ConsequenceForecaster.js +286 -0
- package/dist/orchestrator/CronManager.js +286 -0
- package/dist/orchestrator/DynamicAgentBuilder.js +48 -0
- package/dist/orchestrator/DynamicAgentExecutor.js +15 -0
- package/dist/orchestrator/EnterpriseAgenticCore.js +276 -0
- package/dist/orchestrator/ExecutionBoard.js +86 -0
- package/dist/orchestrator/ExecutionIntent.js +13 -0
- package/dist/orchestrator/ExecutionModePolicy.js +48 -0
- package/dist/orchestrator/ExecutionRouter.js +9 -0
- package/dist/orchestrator/ExecutionState.js +20 -0
- package/dist/orchestrator/ExecutorHealth.js +86 -0
- package/dist/orchestrator/ExternalCatalogRegistry.js +83 -0
- package/dist/orchestrator/Gatekeeper.js +414 -0
- package/dist/orchestrator/Gateway.js +508 -0
- package/dist/orchestrator/GovernanceConsentStore.js +66 -0
- package/dist/orchestrator/GovernanceLayer.js +179 -0
- package/dist/orchestrator/GovernancePolicyStore.js +53 -0
- package/dist/orchestrator/GovernanceScopeLedger.js +134 -0
- package/dist/orchestrator/GovernanceScopePolicy.js +67 -0
- package/dist/orchestrator/IntentClassifier.js +45 -0
- package/dist/orchestrator/JobLifecycle.js +91 -0
- package/dist/orchestrator/LearningRouter.js +24 -0
- package/dist/orchestrator/MediaManager.js +92 -0
- package/dist/orchestrator/MemoryCuratorAgent.js +41 -0
- package/dist/orchestrator/MissionState.js +155 -0
- package/dist/orchestrator/ModelManager.js +84 -0
- package/dist/orchestrator/OperatingSystem.js +71 -0
- package/dist/orchestrator/OperationalMemoryStore.js +94 -0
- package/dist/orchestrator/OptimizationLoop.js +72 -0
- package/dist/orchestrator/OrchestrationLoop.js +905 -0
- package/dist/orchestrator/OrgStructure.js +88 -0
- package/dist/orchestrator/OutcomeSimulator.js +46 -0
- package/dist/orchestrator/ParallelOrchestrationLoop.js +36 -0
- package/dist/orchestrator/PerformanceScorecard.js +105 -0
- package/dist/orchestrator/PlannerAgent.js +46 -0
- package/dist/orchestrator/ProcessSandbox.js +129 -0
- package/dist/orchestrator/PromotionPipeline.js +74 -0
- package/dist/orchestrator/PromotionReviewGate.js +11 -0
- package/dist/orchestrator/QueueScheduler.js +260 -0
- package/dist/orchestrator/ReleaseGate.js +36 -0
- package/dist/orchestrator/ReleaseWorkflow.js +68 -0
- package/dist/orchestrator/RemotePublisher.js +139 -0
- package/dist/orchestrator/ReuseEngine.js +89 -0
- package/dist/orchestrator/ReviewerAgent.js +49 -0
- package/dist/orchestrator/RoleHandoff.js +65 -0
- package/dist/orchestrator/RuntimeHealthMonitor.js +143 -0
- package/dist/orchestrator/RuntimePolicy.js +105 -0
- package/dist/orchestrator/RuntimeProbe.js +97 -0
- package/dist/orchestrator/RuntimeRegistry.js +73 -0
- package/dist/orchestrator/SandboxPolicy.js +22 -0
- package/dist/orchestrator/SecurityDownloadGuard.js +169 -0
- package/dist/orchestrator/SecurityEventStore.js +58 -0
- package/dist/orchestrator/ServiceCompletionPolicy.js +36 -0
- package/dist/orchestrator/ServiceState.js +195 -0
- package/dist/orchestrator/SkillCreator.js +404 -0
- package/dist/orchestrator/SkillLearningLoop.js +57 -0
- package/dist/orchestrator/SkillManager.js +75 -0
- package/dist/orchestrator/SkillNetwork.js +29 -0
- package/dist/orchestrator/SkillRegistryV2.js +28 -0
- package/dist/orchestrator/SkillScoring.js +70 -0
- package/dist/orchestrator/SquadAutoCreator.js +64 -0
- package/dist/orchestrator/SquadCreator.js +727 -0
- package/dist/orchestrator/SquadRegistry.js +28 -0
- package/dist/orchestrator/SquadRouter.js +33 -0
- package/dist/orchestrator/SquadScoring.js +70 -0
- package/dist/orchestrator/SubagentLifecycle.js +90 -0
- package/dist/orchestrator/SynthesizerAgent.js +48 -0
- package/dist/orchestrator/SystemDoctor.js +224 -0
- package/dist/orchestrator/TaskExecutor.js +422 -0
- package/dist/orchestrator/TeammateBoard.js +61 -0
- package/dist/orchestrator/TestHarness.js +184 -0
- package/dist/orchestrator/VoiceManager.js +203 -0
- package/dist/orchestrator/VoiceRouter.js +89 -0
- package/dist/orchestrator/capability/CapabilityGenesisEngine.js +278 -0
- package/dist/orchestrator/capability/CapabilityPackParser.js +223 -0
- package/dist/orchestrator/capability/CapabilityPackSchema.js +62 -0
- package/dist/orchestrator/capability/CapabilityPackState.js +163 -0
- package/dist/orchestrator/providers/AgentProvider.js +2 -0
- package/dist/orchestrator/providers/CapabilityProvider.js +12 -0
- package/dist/orchestrator/providers/CloudAgentProvider.js +55 -0
- package/dist/orchestrator/providers/CloudSkillProvider.js +55 -0
- package/dist/orchestrator/providers/CloudSquadProvider.js +55 -0
- package/dist/orchestrator/providers/CompositeAgentProvider.js +16 -0
- package/dist/orchestrator/providers/CompositeCapabilityProvider.js +25 -0
- package/dist/orchestrator/providers/CompositeSkillProvider.js +16 -0
- package/dist/orchestrator/providers/CompositeSquadProvider.js +16 -0
- package/dist/orchestrator/providers/CompositeWorkflowProvider.js +46 -0
- package/dist/orchestrator/providers/FileAgentProvider.js +105 -0
- package/dist/orchestrator/providers/FileCapabilityProvider.js +106 -0
- package/dist/orchestrator/providers/FileSkillProvider.js +65 -0
- package/dist/orchestrator/providers/FileSquadProvider.js +69 -0
- package/dist/orchestrator/providers/FileWorkflowProvider.js +103 -0
- package/dist/orchestrator/providers/SkillProvider.js +2 -0
- package/dist/orchestrator/providers/SquadProvider.js +2 -0
- package/dist/orchestrator/toolset/ToolsetGuard.js +69 -0
- package/dist/orchestrator/toolset/ToolsetRegistry.js +65 -0
- package/dist/orchestrator/toolset/ToolsetSchema.js +21 -0
- package/dist/orchestrator/util/AtomicWriter.js +204 -0
- package/dist/orchestrator/util/DistributedLock.js +232 -0
- package/dist/orchestrator/util/TemplateRenderer.js +87 -0
- package/dist/orchestrator/util/WatchdogHeartbeat.js +116 -0
- package/dist/orchestrator/workflow/ConditionParser.js +232 -0
- package/dist/orchestrator/workflow/WorkflowEngine.js +379 -0
- package/dist/orchestrator/workflow/WorkflowParser.js +368 -0
- package/dist/orchestrator/workflow/WorkflowSchema.js +65 -0
- package/dist/orchestrator/workflow/WorkflowState.js +11 -0
- package/dist/reversa/ReversaAgent.js +134 -0
- package/dist/reversa/ReversaContracts.js +62 -0
- package/dist/reversa/ReversaExecutors.js +65 -0
- package/dist/skills/SkillRegistry.js +71 -0
- package/dist/squads/SquadManager.js +87 -0
- package/dist/test_admin_teams_networks.js +54 -0
- package/dist/test_agent_team_skill_network.js +15 -0
- package/dist/test_aiobuilder_cli_parity.js +169 -0
- package/dist/test_ask_exit.js +73 -0
- package/dist/test_atomic_writer.js +209 -0
- package/dist/test_autonomous_soak.js +141 -0
- package/dist/test_benchmark_engine.js +41 -0
- package/dist/test_brain_error_diagnostics.js +51 -0
- package/dist/test_brain_fallback_chain.js +93 -0
- package/dist/test_capability_genesis_engine.js +225 -0
- package/dist/test_capability_pack_schema.js +214 -0
- package/dist/test_catalog_quality.js +150 -0
- package/dist/test_cli_crud_roundtrip.js +154 -0
- package/dist/test_cli_diagnostics.js +131 -0
- package/dist/test_cli_doc_parity.js +126 -0
- package/dist/test_cli_help_surface.js +106 -0
- package/dist/test_cli_service_commands.js +83 -0
- package/dist/test_consequence_forecast_brain.js +165 -0
- package/dist/test_consequence_forecaster.js +24 -0
- package/dist/test_conversation_memory.js +36 -0
- package/dist/test_create_entities.js +54 -0
- package/dist/test_creator_placeholders_completed.js +177 -0
- package/dist/test_cron_manager.js +123 -0
- package/dist/test_daemon_sigterm.js +72 -0
- package/dist/test_deep_research_capability.js +87 -0
- package/dist/test_designmd_import_registry.js +16 -0
- package/dist/test_designmd_mode.js +50 -0
- package/dist/test_designmd_mode_workspace.js +13 -0
- package/dist/test_dist_templates_layout.js +135 -0
- package/dist/test_distributed_lock.js +201 -0
- package/dist/test_distribution_installability.js +67 -0
- package/dist/test_doctor_sandbox_check.js +44 -0
- package/dist/test_dream_organizer.js +25 -0
- package/dist/test_dual_mode.js +15 -0
- package/dist/test_enterprise_agentic_core.js +128 -0
- package/dist/test_forecast_brain_wiring.js +87 -0
- package/dist/test_gateway_telegram_guardrails.js +52 -0
- package/dist/test_governance.js +34 -0
- package/dist/test_governance_advanced.js +75 -0
- package/dist/test_governance_scope_ledger.js +147 -0
- package/dist/test_governance_v13_policies.js +44 -0
- package/dist/test_guided_creator_cli.js +100 -0
- package/dist/test_host_install_e2e.js +324 -0
- package/dist/test_host_installer.js +259 -0
- package/dist/test_host_installers_gemini_codex.js +95 -0
- package/dist/test_host_uninstaller.js +295 -0
- package/dist/test_install_flow.js +70 -0
- package/dist/test_install_flow_host_validation.js +143 -0
- package/dist/test_install_wizard.js +272 -0
- package/dist/test_integration_gemini_live.js +95 -0
- package/dist/test_integration_http_trigger_live.js +154 -0
- package/dist/test_integration_telegram_live.js +102 -0
- package/dist/test_job_lifecycle.js +16 -0
- package/dist/test_memory_orchestrator.js +33 -0
- package/dist/test_memory_promotion.js +36 -0
- package/dist/test_memory_retention.js +37 -0
- package/dist/test_mission_checkpoint.js +204 -0
- package/dist/test_multi_host_docs_parity.js +125 -0
- package/dist/test_openlife_auto_creator_routing.js +69 -0
- package/dist/test_openlife_evolution_surface.js +77 -0
- package/dist/test_openlife_gatekeeper_routing.js +15 -0
- package/dist/test_openlife_routing_surface.js +27 -0
- package/dist/test_openlife_runtime_source_truth.js +25 -0
- package/dist/test_operating_system.js +45 -0
- package/dist/test_optimization_loop.js +38 -0
- package/dist/test_orchestration_assets_lifecycle.js +78 -0
- package/dist/test_outcome_simulator.js +38 -0
- package/dist/test_performance_latency.js +215 -0
- package/dist/test_performance_scorecard.js +38 -0
- package/dist/test_phase1_check_exit.js +103 -0
- package/dist/test_phase6_board.js +31 -0
- package/dist/test_phase6_cadence.js +29 -0
- package/dist/test_phase6_ops.js +37 -0
- package/dist/test_post_mission_evaluation.js +190 -0
- package/dist/test_process_sandbox.js +88 -0
- package/dist/test_profile_toolset_mcp.js +125 -0
- package/dist/test_queue_scheduler.js +239 -0
- package/dist/test_release_gate.js +23 -0
- package/dist/test_remote_publish.js +193 -0
- package/dist/test_reversa_contracts_e2e.js +48 -0
- package/dist/test_reversa_export_and_strict.js +51 -0
- package/dist/test_reversa_full_execution.js +12 -0
- package/dist/test_reversa_lite.js +9 -0
- package/dist/test_royal_stack_golden.js +179 -0
- package/dist/test_runtime_health_backoff.js +154 -0
- package/dist/test_runtime_policy.js +26 -0
- package/dist/test_runtime_probe.js +19 -0
- package/dist/test_runtime_profile_oauth_only.js +262 -0
- package/dist/test_runtime_registry.js +11 -0
- package/dist/test_security_download_and_scan.js +103 -0
- package/dist/test_security_download_guard.js +14 -0
- package/dist/test_service_command_surface.js +12 -0
- package/dist/test_service_completion_policy.js +32 -0
- package/dist/test_service_guardrails_delete.js +12 -0
- package/dist/test_service_mode_explicit_only.js +174 -0
- package/dist/test_sources_import_ref.js +46 -0
- package/dist/test_sources_scaffold.js +43 -0
- package/dist/test_squad_skill_creator.js +305 -0
- package/dist/test_squad_skill_design_llm.js +176 -0
- package/dist/test_subsystems_org_state.js +271 -0
- package/dist/test_subsystems_promotion_memory_assets.js +343 -0
- package/dist/test_subsystems_routing_governance.js +234 -0
- package/dist/test_task_executor_sandbox_optin.js +127 -0
- package/dist/test_teammate_learning.js +15 -0
- package/dist/test_telegram_delete_guardrail.js +21 -0
- package/dist/test_toolset_enforcement.js +188 -0
- package/dist/test_trigger_basic_auth.js +112 -0
- package/dist/test_util/doc_parity.js +120 -0
- package/dist/test_v15_e2e_integration.js +207 -0
- package/dist/test_watchdog_heartbeat.js +152 -0
- package/dist/test_workflow_condition_parser.js +63 -0
- package/dist/test_workflow_e2e.js +240 -0
- package/dist/test_workflow_engine.js +330 -0
- package/dist/test_workflow_parser.js +245 -0
- package/dist/test_workflow_schema_backward_compat.js +197 -0
- package/dist-templates/README.md +91 -0
- package/dist-templates/claude-code/agents/openlife-atlas.md +52 -0
- package/dist-templates/claude-code/agents/openlife-forge.md +42 -0
- package/dist-templates/claude-code/agents/openlife-genesis.md +59 -0
- package/dist-templates/claude-code/agents/openlife-lyra.md +40 -0
- package/dist-templates/claude-code/agents/openlife-maestro.md +45 -0
- package/dist-templates/claude-code/commands/openlife/ask.md +14 -0
- package/dist-templates/claude-code/commands/openlife/doctor.md +19 -0
- package/dist-templates/claude-code/commands/openlife/dream.md +20 -0
- package/dist-templates/claude-code/commands/openlife/status.md +14 -0
- package/dist-templates/claude-code/mcp/openlife-orchestrator.json +46 -0
- package/dist-templates/codex/README.md +7 -0
- package/dist-templates/codex/agents/openlife-atlas.md +52 -0
- package/dist-templates/codex/agents/openlife-forge.md +42 -0
- package/dist-templates/codex/agents/openlife-genesis.md +59 -0
- package/dist-templates/codex/agents/openlife-lyra.md +40 -0
- package/dist-templates/codex/agents/openlife-maestro.md +45 -0
- package/dist-templates/codex/commands/openlife/ask.md +14 -0
- package/dist-templates/codex/commands/openlife/doctor.md +19 -0
- package/dist-templates/codex/commands/openlife/dream.md +20 -0
- package/dist-templates/codex/commands/openlife/status.md +14 -0
- package/dist-templates/codex/mcp/openlife-orchestrator.json +46 -0
- package/dist-templates/gemini-cli/README.md +8 -0
- package/dist-templates/gemini-cli/agents/openlife-atlas.md +52 -0
- package/dist-templates/gemini-cli/agents/openlife-forge.md +42 -0
- package/dist-templates/gemini-cli/agents/openlife-genesis.md +59 -0
- package/dist-templates/gemini-cli/agents/openlife-lyra.md +40 -0
- package/dist-templates/gemini-cli/agents/openlife-maestro.md +45 -0
- package/dist-templates/gemini-cli/commands/openlife/ask.md +14 -0
- package/dist-templates/gemini-cli/commands/openlife/doctor.md +19 -0
- package/dist-templates/gemini-cli/commands/openlife/dream.md +20 -0
- package/dist-templates/gemini-cli/commands/openlife/status.md +14 -0
- package/dist-templates/gemini-cli/mcp/openlife-orchestrator.json +46 -0
- package/dist-templates/skill-template/README.md +34 -0
- package/dist-templates/skill-template/SKILL.md.template +59 -0
- package/dist-templates/squad-template/README.md +82 -0
- package/dist-templates/squad-template/SQUAD.md.template +51 -0
- package/dist-templates/squad-template/agent-template.md +51 -0
- package/dist-templates/squad-template/checklist-template.md +25 -0
- package/dist-templates/squad-template/task-template.md +36 -0
- package/dist-templates/workflows/PORTED_WORKFLOWS.md +60 -0
- package/dist-templates/workflows/brownfield-discovery.yaml +137 -0
- package/dist-templates/workflows/greenfield-fullstack.yaml +132 -0
- package/dist-templates/workflows/qa-loop.yaml +125 -0
- package/dist-templates/workflows/story-development-cycle.yaml +80 -0
- package/docs/CHANGELOG_FEATURE_ROLLOUT_DESIGNMD.md +43 -0
- package/docs/EXTERNAL_SOURCES_AND_SECURITY_GUARD.md +33 -0
- package/docs/OPENLIFE_AUDIT_2026-05-06.md +170 -0
- package/docs/OPENLIFE_CONSOLIDATED_PLAN_2026-05-06.md +299 -0
- package/docs/OPENLIFE_DUAL_MODE_IMPLEMENTATION_PLAN.md +205 -0
- package/docs/OPENLIFE_EVOLUTION_SURFACE_2026-05-07.md +53 -0
- package/docs/OPENLIFE_SKILLS_IMPORT_2026-05-07.json +223 -0
- package/docs/OPENLIFE_SQUADS_IMPORT_2026-05-07.json +184 -0
- package/docs/PAPERCLIP_OPENLIFE_INVESTIGATION.md +85 -0
- package/docs/README.md +28 -0
- package/docs/RELEASE_ORGANIZATION_PLAN.md +164 -0
- package/docs/audit/CLI-EXECUTION-RESULTS.md +113 -0
- package/docs/audit/CLI-MATRIX.md +556 -0
- package/docs/audit/DOC-PARITY-GAPS.md +351 -0
- package/docs/audit/ORCHESTRATOR-MATRIX.md +136 -0
- package/docs/audit/TEST-COVERAGE-GAPS.md +334 -0
- package/docs/audit/integrations/SKIPPED.md +101 -0
- package/docs/autonomous-install.md +79 -0
- package/docs/capability-genesis.md +137 -0
- package/docs/capability-pack-schema.md +157 -0
- package/docs/commands.md +82 -0
- package/docs/deep-research-capability.md +114 -0
- package/docs/development/typescript-conventions.md +95 -0
- package/docs/host-installers.md +68 -0
- package/docs/install/aiobuilder.md +70 -0
- package/docs/install/claude-code.md +83 -0
- package/docs/install/codex.md +64 -0
- package/docs/install/gemini-cli.md +64 -0
- package/docs/install/runtime-profiles.md +83 -0
- package/docs/openlife-agent-os-blueprint.md +114 -0
- package/docs/openlife-install-backlog.md +115 -0
- package/docs/openlife-install-spec.md +306 -0
- package/docs/operations/CLOUD_CUTOVER_AUDIT.md +37 -0
- package/docs/operations/PHASE_PROGRESS_CONTINUATION.md +24 -0
- package/docs/performance-benchmarks.md +83 -0
- package/docs/planning/v1.3-capability-genesis.md +157 -0
- package/docs/plans/2026-05-05-admin-interface-professional-dark-premium-plan.md +84 -0
- package/docs/plans/2026-05-05-openlife-autonomous-domain-marketplace-masterplan.md +122 -0
- package/docs/quickstart.md +60 -0
- package/docs/release-process.md +236 -0
- package/docs/roadmap/OPENLIFE_MASTER_PLAN_CLOUD_V3.md +97 -0
- package/docs/sandboxing-research.md +117 -0
- package/docs/stories/epic-feature-audit/1.1.story.md +84 -0
- package/docs/stories/epic-feature-audit/1.2.story.md +102 -0
- package/docs/stories/epic-feature-audit/1.3.story.md +93 -0
- package/docs/stories/epic-feature-audit/1.5.story.md +121 -0
- package/docs/stories/epic-feature-audit/1.6.story.md +80 -0
- package/docs/stories/epic-feature-completeness/2.1.story.md +70 -0
- package/docs/stories/epic-feature-completeness/2.2.story.md +49 -0
- package/docs/stories/epic-feature-completeness/2.3.story.md +74 -0
- package/docs/stories/epic-feature-completeness/2.4.story.md +71 -0
- package/docs/stories/epic-feature-completeness/3.1.story.md +56 -0
- package/docs/stories/epic-feature-completeness/3.2.story.md +80 -0
- package/docs/stories/epic-feature-completeness/3.3.story.md +68 -0
- package/docs/stories/epic-feature-completeness/3.4.story.md +71 -0
- package/docs/stories/epic-feature-completeness/3.5.story.md +72 -0
- package/docs/stories/epic-feature-completeness/3.6.story.md +69 -0
- package/docs/stories/epic-feature-completeness/3.7.story.md +68 -0
- package/docs/stories/epic-feature-completeness/3.8.story.md +57 -0
- package/docs/toolset-enforcement.md +122 -0
- package/docs/v1.4-changelog.md +159 -0
- package/docs/v1.5-changelog.md +106 -0
- package/docs/v1.5-roadmap.md +121 -0
- package/docs/v1.6-changelog.md +67 -0
- package/docs/v1.6-roadmap.md +89 -0
- package/docs/v1.7-changelog.md +98 -0
- package/docs/workflow-schema.md +177 -0
- package/package.json +177 -0
- package/scripts/clean-test-pollution.js +61 -0
- package/scripts/openlife-agent-start.sh +6 -0
- package/scripts/openlife-agent.service.example +13 -0
- package/scripts/openlife-agent.supervisord.conf.example +8 -0
- package/scripts/openlife-autonomous-install.sh +29 -0
- package/scripts/postinstall-check.sh +37 -0
|
@@ -0,0 +1,727 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* SquadCreator — authoring system for squads under `.catalog/squads/`.
|
|
4
|
+
*
|
|
5
|
+
* Story 5.1 — OpenLife v1.2 Royal Stack.
|
|
6
|
+
*
|
|
7
|
+
* Replaces the old `aiobuilder create-squad` stub (which wrote a 16-line
|
|
8
|
+
* markdown file with hardcoded frontmatter) with a real authoring
|
|
9
|
+
* pipeline that renders `dist-templates/squad-template/` into a
|
|
10
|
+
* structured squad directory including agents, tasks, and a checklist.
|
|
11
|
+
*
|
|
12
|
+
* v1.2 implements 4 of the 8 methods promised by the SQUAD.md spec:
|
|
13
|
+
* - create — full template rendering + directory layout
|
|
14
|
+
* - validate — frontmatter + components reference check
|
|
15
|
+
* - list — delegate to SquadRegistry composite read
|
|
16
|
+
* - analyze — surface coverage gaps (missing tasks/orphan agents)
|
|
17
|
+
*
|
|
18
|
+
* 4 are placeholders for v1.3 (Capability Genesis Engine):
|
|
19
|
+
* - design — LLM-driven proposal from a brief (needs Brain wiring)
|
|
20
|
+
* - migrate — schema migration between versions
|
|
21
|
+
* - extend — append components to an existing squad
|
|
22
|
+
* - publish — promote draft→active, sync to remote registry
|
|
23
|
+
*/
|
|
24
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
25
|
+
if (k2 === undefined) k2 = k;
|
|
26
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
27
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
28
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
29
|
+
}
|
|
30
|
+
Object.defineProperty(o, k2, desc);
|
|
31
|
+
}) : (function(o, m, k, k2) {
|
|
32
|
+
if (k2 === undefined) k2 = k;
|
|
33
|
+
o[k2] = m[k];
|
|
34
|
+
}));
|
|
35
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
36
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
37
|
+
}) : function(o, v) {
|
|
38
|
+
o["default"] = v;
|
|
39
|
+
});
|
|
40
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
41
|
+
var ownKeys = function(o) {
|
|
42
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
43
|
+
var ar = [];
|
|
44
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
45
|
+
return ar;
|
|
46
|
+
};
|
|
47
|
+
return ownKeys(o);
|
|
48
|
+
};
|
|
49
|
+
return function (mod) {
|
|
50
|
+
if (mod && mod.__esModule) return mod;
|
|
51
|
+
var result = {};
|
|
52
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
53
|
+
__setModuleDefault(result, mod);
|
|
54
|
+
return result;
|
|
55
|
+
};
|
|
56
|
+
})();
|
|
57
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
58
|
+
exports.SquadCreator = void 0;
|
|
59
|
+
exports.extractFirstJson = extractFirstJson;
|
|
60
|
+
const fs = __importStar(require("fs"));
|
|
61
|
+
const path = __importStar(require("path"));
|
|
62
|
+
const crypto = __importStar(require("crypto"));
|
|
63
|
+
const AtomicWriter_1 = require("./util/AtomicWriter");
|
|
64
|
+
const TemplateRenderer_1 = require("./util/TemplateRenderer");
|
|
65
|
+
const ToolsetGuard_1 = require("./toolset/ToolsetGuard");
|
|
66
|
+
class SquadCreator {
|
|
67
|
+
catalogRoot;
|
|
68
|
+
templateRoot;
|
|
69
|
+
constructor(opts = {}) {
|
|
70
|
+
this.catalogRoot = opts.catalogRoot || path.join(process.cwd(), '.catalog', 'squads');
|
|
71
|
+
this.templateRoot = opts.templateRoot || path.resolve(__dirname, '..', '..', 'dist-templates', 'squad-template');
|
|
72
|
+
}
|
|
73
|
+
// ─────────────────────────────────────────────────────────
|
|
74
|
+
// create — full directory rendering from templates
|
|
75
|
+
// ─────────────────────────────────────────────────────────
|
|
76
|
+
create(proposal) {
|
|
77
|
+
(0, ToolsetGuard_1.assertToolsetAllowed)('squads', 'SquadCreator.create');
|
|
78
|
+
if (!this.isValidId(proposal.id)) {
|
|
79
|
+
return { ok: false, error: 'invalid_squad_id', detail: 'must match /^[a-z0-9][a-z0-9._-]+$/i' };
|
|
80
|
+
}
|
|
81
|
+
if (!Array.isArray(proposal.agents) || proposal.agents.length === 0) {
|
|
82
|
+
return { ok: false, error: 'no_agents', detail: 'a squad needs at least one agent' };
|
|
83
|
+
}
|
|
84
|
+
const squadDir = path.join(this.catalogRoot, proposal.id);
|
|
85
|
+
if (fs.existsSync(squadDir)) {
|
|
86
|
+
return { ok: false, error: 'squad_already_exists', detail: squadDir };
|
|
87
|
+
}
|
|
88
|
+
const filesCreated = [];
|
|
89
|
+
try {
|
|
90
|
+
fs.mkdirSync(squadDir, { recursive: true });
|
|
91
|
+
fs.mkdirSync(path.join(squadDir, 'agents'), { recursive: true });
|
|
92
|
+
if (proposal.tasks?.length)
|
|
93
|
+
fs.mkdirSync(path.join(squadDir, 'tasks'), { recursive: true });
|
|
94
|
+
if (proposal.checklists?.length)
|
|
95
|
+
fs.mkdirSync(path.join(squadDir, 'checklists'), { recursive: true });
|
|
96
|
+
// Render SQUAD.md
|
|
97
|
+
const squadVars = this.buildSquadVars(proposal);
|
|
98
|
+
const squadContent = (0, TemplateRenderer_1.renderTemplateFile)(path.join(this.templateRoot, 'SQUAD.md.template'), squadVars);
|
|
99
|
+
const squadFile = path.join(squadDir, 'SQUAD.md');
|
|
100
|
+
(0, AtomicWriter_1.writeStringAtomic)(squadFile, squadContent);
|
|
101
|
+
filesCreated.push(squadFile);
|
|
102
|
+
// Render each agent
|
|
103
|
+
for (const agent of proposal.agents) {
|
|
104
|
+
const agentVars = this.buildAgentVars(agent, proposal);
|
|
105
|
+
const agentContent = (0, TemplateRenderer_1.renderTemplateFile)(path.join(this.templateRoot, 'agent-template.md'), agentVars);
|
|
106
|
+
const agentFile = path.join(squadDir, 'agents', `${agent.id}.md`);
|
|
107
|
+
(0, AtomicWriter_1.writeStringAtomic)(agentFile, agentContent);
|
|
108
|
+
filesCreated.push(agentFile);
|
|
109
|
+
}
|
|
110
|
+
// Render each task
|
|
111
|
+
for (const task of proposal.tasks || []) {
|
|
112
|
+
const taskVars = this.buildTaskVars(task, proposal);
|
|
113
|
+
const taskContent = (0, TemplateRenderer_1.renderTemplateFile)(path.join(this.templateRoot, 'task-template.md'), taskVars);
|
|
114
|
+
const taskFile = path.join(squadDir, 'tasks', `${task.id}.md`);
|
|
115
|
+
(0, AtomicWriter_1.writeStringAtomic)(taskFile, taskContent);
|
|
116
|
+
filesCreated.push(taskFile);
|
|
117
|
+
}
|
|
118
|
+
// Render each checklist
|
|
119
|
+
for (const checklist of proposal.checklists || []) {
|
|
120
|
+
const ckVars = {
|
|
121
|
+
CHECKLIST_NAME: checklist.id,
|
|
122
|
+
SQUAD_NAME: proposal.name,
|
|
123
|
+
PURPOSE: checklist.purpose,
|
|
124
|
+
ITEM_1: checklist.items[0] || '',
|
|
125
|
+
ITEM_2: checklist.items[1] || '',
|
|
126
|
+
ITEM_3: checklist.items[2] || '',
|
|
127
|
+
ITEM_4: checklist.items[3] || '',
|
|
128
|
+
ITEM_5: checklist.items[4] || '',
|
|
129
|
+
};
|
|
130
|
+
const ckContent = (0, TemplateRenderer_1.renderTemplateFile)(path.join(this.templateRoot, 'checklist-template.md'), ckVars);
|
|
131
|
+
const ckFile = path.join(squadDir, 'checklists', `${checklist.id}.md`);
|
|
132
|
+
(0, AtomicWriter_1.writeStringAtomic)(ckFile, ckContent);
|
|
133
|
+
filesCreated.push(ckFile);
|
|
134
|
+
}
|
|
135
|
+
return { ok: true, squadId: proposal.id, squadDir, filesCreated };
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
// Roll back partial directory on error
|
|
139
|
+
try {
|
|
140
|
+
fs.rmSync(squadDir, { recursive: true, force: true });
|
|
141
|
+
}
|
|
142
|
+
catch { /* ignore */ }
|
|
143
|
+
return { ok: false, error: 'render_failed', detail: err instanceof Error ? err.message : String(err) };
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// ─────────────────────────────────────────────────────────
|
|
147
|
+
// validate — frontmatter + component reference check
|
|
148
|
+
// ─────────────────────────────────────────────────────────
|
|
149
|
+
validate(squadId) {
|
|
150
|
+
const errors = [];
|
|
151
|
+
const warnings = [];
|
|
152
|
+
const squadDir = path.join(this.catalogRoot, squadId);
|
|
153
|
+
const squadFile = path.join(squadDir, 'SQUAD.md');
|
|
154
|
+
if (!fs.existsSync(squadDir)) {
|
|
155
|
+
errors.push(`squad directory not found: ${squadDir}`);
|
|
156
|
+
return { ok: false, squadId, errors, warnings };
|
|
157
|
+
}
|
|
158
|
+
if (!fs.existsSync(squadFile)) {
|
|
159
|
+
errors.push('SQUAD.md missing');
|
|
160
|
+
return { ok: false, squadId, errors, warnings };
|
|
161
|
+
}
|
|
162
|
+
const content = fs.readFileSync(squadFile, 'utf-8');
|
|
163
|
+
// Frontmatter must have id/name/status
|
|
164
|
+
const frontmatter = this.parseFrontmatter(content);
|
|
165
|
+
if (!frontmatter.id)
|
|
166
|
+
errors.push('frontmatter missing id');
|
|
167
|
+
if (!frontmatter.name)
|
|
168
|
+
errors.push('frontmatter missing name');
|
|
169
|
+
if (!frontmatter.status)
|
|
170
|
+
errors.push('frontmatter missing status');
|
|
171
|
+
if (frontmatter.id && frontmatter.id !== squadId) {
|
|
172
|
+
errors.push(`frontmatter id '${frontmatter.id}' does not match directory '${squadId}'`);
|
|
173
|
+
}
|
|
174
|
+
// Component files referenced in SQUAD.md should exist
|
|
175
|
+
const componentRefs = this.parseComponentRefs(content);
|
|
176
|
+
for (const refPath of componentRefs.agents) {
|
|
177
|
+
const full = path.join(squadDir, 'agents', refPath);
|
|
178
|
+
if (!fs.existsSync(full))
|
|
179
|
+
warnings.push(`referenced agent not found: agents/${refPath}`);
|
|
180
|
+
}
|
|
181
|
+
for (const refPath of componentRefs.tasks) {
|
|
182
|
+
const full = path.join(squadDir, 'tasks', refPath);
|
|
183
|
+
if (!fs.existsSync(full))
|
|
184
|
+
warnings.push(`referenced task not found: tasks/${refPath}`);
|
|
185
|
+
}
|
|
186
|
+
return { ok: errors.length === 0, squadId, errors, warnings };
|
|
187
|
+
}
|
|
188
|
+
// ─────────────────────────────────────────────────────────
|
|
189
|
+
// list — direct read of .catalog/squads/
|
|
190
|
+
// ─────────────────────────────────────────────────────────
|
|
191
|
+
list(filter) {
|
|
192
|
+
if (!fs.existsSync(this.catalogRoot))
|
|
193
|
+
return [];
|
|
194
|
+
const out = [];
|
|
195
|
+
for (const entry of fs.readdirSync(this.catalogRoot)) {
|
|
196
|
+
const squadFile = path.join(this.catalogRoot, entry, 'SQUAD.md');
|
|
197
|
+
if (!fs.existsSync(squadFile))
|
|
198
|
+
continue;
|
|
199
|
+
const fm = this.parseFrontmatter(fs.readFileSync(squadFile, 'utf-8'));
|
|
200
|
+
const item = {
|
|
201
|
+
id: fm.id || entry,
|
|
202
|
+
name: fm.name || entry,
|
|
203
|
+
status: fm.status || 'unknown',
|
|
204
|
+
};
|
|
205
|
+
if (filter?.status && item.status !== filter.status)
|
|
206
|
+
continue;
|
|
207
|
+
out.push(item);
|
|
208
|
+
}
|
|
209
|
+
return out;
|
|
210
|
+
}
|
|
211
|
+
// ─────────────────────────────────────────────────────────
|
|
212
|
+
// analyze — coverage gaps + suggestions
|
|
213
|
+
// ─────────────────────────────────────────────────────────
|
|
214
|
+
analyze(squadId) {
|
|
215
|
+
const squadDir = path.join(this.catalogRoot, squadId);
|
|
216
|
+
if (!fs.existsSync(squadDir))
|
|
217
|
+
return null;
|
|
218
|
+
const agentsDir = path.join(squadDir, 'agents');
|
|
219
|
+
const tasksDir = path.join(squadDir, 'tasks');
|
|
220
|
+
const workflowsDir = path.join(squadDir, 'workflows');
|
|
221
|
+
const checklistsDir = path.join(squadDir, 'checklists');
|
|
222
|
+
const listFiles = (d) => (fs.existsSync(d) && fs.statSync(d).isDirectory() ? fs.readdirSync(d) : []);
|
|
223
|
+
const agentFiles = listFiles(agentsDir).filter((f) => f.endsWith('.md'));
|
|
224
|
+
const taskFiles = listFiles(tasksDir).filter((f) => f.endsWith('.md'));
|
|
225
|
+
const workflowFiles = listFiles(workflowsDir).filter((f) => f.endsWith('.yaml') || f.endsWith('.yml'));
|
|
226
|
+
const checklistFiles = listFiles(checklistsDir).filter((f) => f.endsWith('.md'));
|
|
227
|
+
// Orphan agents: agent files not referenced in any task
|
|
228
|
+
const orphanAgents = [];
|
|
229
|
+
for (const af of agentFiles) {
|
|
230
|
+
const aid = af.replace(/\.md$/, '');
|
|
231
|
+
const referenced = taskFiles.some((tf) => fs.readFileSync(path.join(tasksDir, tf), 'utf-8').includes(aid));
|
|
232
|
+
if (!referenced && taskFiles.length > 0)
|
|
233
|
+
orphanAgents.push(aid);
|
|
234
|
+
}
|
|
235
|
+
// Unreferenced tasks: task files whose owner agent file is missing
|
|
236
|
+
const unreferencedTasks = [];
|
|
237
|
+
for (const tf of taskFiles) {
|
|
238
|
+
const content = fs.readFileSync(path.join(tasksDir, tf), 'utf-8');
|
|
239
|
+
const ownerMatch = content.match(/Owner:\s*([\w-]+)/);
|
|
240
|
+
if (ownerMatch && !agentFiles.includes(`${ownerMatch[1]}.md`)) {
|
|
241
|
+
unreferencedTasks.push(tf.replace(/\.md$/, ''));
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
const suggestions = [];
|
|
245
|
+
if (agentFiles.length === 0)
|
|
246
|
+
suggestions.push('squad has no agents — at least one is required');
|
|
247
|
+
if (taskFiles.length === 0)
|
|
248
|
+
suggestions.push('squad has no tasks — consider adding at least one canonical task');
|
|
249
|
+
if (workflowFiles.length === 0)
|
|
250
|
+
suggestions.push('squad has no workflows — consider adding a primary workflow for discoverability');
|
|
251
|
+
if (checklistFiles.length === 0)
|
|
252
|
+
suggestions.push('squad has no quality gates — consider adding a checklist');
|
|
253
|
+
if (orphanAgents.length > 0)
|
|
254
|
+
suggestions.push(`${orphanAgents.length} agent(s) not referenced by any task`);
|
|
255
|
+
if (unreferencedTasks.length > 0)
|
|
256
|
+
suggestions.push(`${unreferencedTasks.length} task(s) reference a missing owner agent`);
|
|
257
|
+
return {
|
|
258
|
+
squadId,
|
|
259
|
+
agentCount: agentFiles.length,
|
|
260
|
+
taskCount: taskFiles.length,
|
|
261
|
+
workflowCount: workflowFiles.length,
|
|
262
|
+
checklistCount: checklistFiles.length,
|
|
263
|
+
orphanAgents,
|
|
264
|
+
unreferencedTasks,
|
|
265
|
+
suggestions,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
// ─────────────────────────────────────────────────────────
|
|
269
|
+
// v1.3 placeholders — stable surface, return clear "not implemented"
|
|
270
|
+
// so callers can wire UI / CLI today and the real implementation
|
|
271
|
+
// arrives behind the same API.
|
|
272
|
+
// ─────────────────────────────────────────────────────────
|
|
273
|
+
/**
|
|
274
|
+
* Propose a SquadProposal from a free-text brief.
|
|
275
|
+
*
|
|
276
|
+
* Story 3.1 — OpenLife v1.3.
|
|
277
|
+
*
|
|
278
|
+
* Heuristic mode: produces a minimum-viable squad that the caller can
|
|
279
|
+
* pass directly to create(). No API call — works offline, no keys
|
|
280
|
+
* needed. Returns the proposal; caller decides whether to call create()
|
|
281
|
+
* with it or refine first. The heuristic picks 3 default agents
|
|
282
|
+
* (lead, executor, reviewer) and one task; for richer drafts, the
|
|
283
|
+
* CapabilityGenesisEngine wires this together with deep-research-style
|
|
284
|
+
* patterns.
|
|
285
|
+
*
|
|
286
|
+
* Brain-mode (live LLM proposal) lands in v1.4; the signature stays
|
|
287
|
+
* stable so callers don't change.
|
|
288
|
+
*/
|
|
289
|
+
design(brief, opts = {}) {
|
|
290
|
+
const trimmed = (brief || '').trim();
|
|
291
|
+
if (!trimmed)
|
|
292
|
+
return { ok: false, error: 'invalid_brief' };
|
|
293
|
+
const mode = opts.mode || 'professional';
|
|
294
|
+
const id = opts.id || slugifyForId(trimmed).slice(0, 50);
|
|
295
|
+
if (!/^[a-z0-9][a-z0-9._-]+$/i.test(id))
|
|
296
|
+
return { ok: false, error: 'invalid_squad_id', };
|
|
297
|
+
// Heuristic agent roster — same shape SquadCreator.create accepts.
|
|
298
|
+
const agents = [
|
|
299
|
+
{ id: `${id}-lead`, name: `${id}-lead`, role: 'orchestrator' },
|
|
300
|
+
{ id: `${id}-executor`, name: `${id}-executor`, role: 'executor' },
|
|
301
|
+
];
|
|
302
|
+
if (mode === 'professional' || mode === 'elite') {
|
|
303
|
+
agents.push({ id: `${id}-reviewer`, name: `${id}-reviewer`, role: 'reviewer' });
|
|
304
|
+
}
|
|
305
|
+
if (mode === 'elite') {
|
|
306
|
+
agents.push({ id: `${id}-strategist`, name: `${id}-strategist`, role: 'strategist' });
|
|
307
|
+
}
|
|
308
|
+
const tasks = [
|
|
309
|
+
{ id: `${id}-default`, ownerAgentId: agents[0].id, purpose: trimmed },
|
|
310
|
+
];
|
|
311
|
+
const checklists = mode === 'elite' ? [{
|
|
312
|
+
id: `${id}-quality-gate`,
|
|
313
|
+
purpose: `quality gate for ${id}`,
|
|
314
|
+
items: ['Brief understood', 'Agents assigned', 'Output validated'],
|
|
315
|
+
}] : [];
|
|
316
|
+
const proposal = {
|
|
317
|
+
id,
|
|
318
|
+
name: id,
|
|
319
|
+
description: trimmed,
|
|
320
|
+
version: '0.1.0',
|
|
321
|
+
author: 'squad-creator.design',
|
|
322
|
+
agents,
|
|
323
|
+
tasks,
|
|
324
|
+
checklists,
|
|
325
|
+
};
|
|
326
|
+
return { ok: true, proposal };
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Story 8.1 — LLM-driven design.
|
|
330
|
+
*
|
|
331
|
+
* If `brain.isAnyProviderAvailable()` returns true, asks the Brain for a
|
|
332
|
+
* structured JSON proposal and parses it. On any parse / API failure,
|
|
333
|
+
* falls back to the heuristic `design()` so the call is never lossy.
|
|
334
|
+
*
|
|
335
|
+
* Signature is additive: existing callers continue using sync `design()`.
|
|
336
|
+
* No env flag — the presence of an API key IS the opt-in (locked decision
|
|
337
|
+
* 2 of the v1.4 plan).
|
|
338
|
+
*/
|
|
339
|
+
async designWithBrain(brain, brief, opts = {}) {
|
|
340
|
+
if (!brain.isAnyProviderAvailable()) {
|
|
341
|
+
const r = this.design(brief, opts);
|
|
342
|
+
return r.ok ? { ok: true, proposal: r.proposal, source: 'heuristic' } : r;
|
|
343
|
+
}
|
|
344
|
+
const trimmed = (brief || '').trim();
|
|
345
|
+
if (!trimmed)
|
|
346
|
+
return { ok: false, error: 'invalid_brief' };
|
|
347
|
+
const mode = opts.mode || 'professional';
|
|
348
|
+
const id = opts.id || slugifyForId(trimmed).slice(0, 50);
|
|
349
|
+
if (!/^[a-z0-9][a-z0-9._-]+$/i.test(id))
|
|
350
|
+
return { ok: false, error: 'invalid_squad_id' };
|
|
351
|
+
const systemPrompt = `You are SquadCreator. Given a brief and an authoring mode (quick|professional|elite), return a STRICT JSON object describing a squad. The output MUST be a single JSON object — no prose, no markdown fences. Shape:
|
|
352
|
+
{
|
|
353
|
+
"agents": [{"id":"<slug>","role":"<role>"}],
|
|
354
|
+
"tasks": [{"id":"<slug>","ownerAgentId":"<agent-id>","purpose":"<one-line>"}],
|
|
355
|
+
"checklists": [{"id":"<slug>","purpose":"<one-line>","items":["..."]}]
|
|
356
|
+
}
|
|
357
|
+
Rules:
|
|
358
|
+
- quick mode: 2 agents (lead, executor), 1 task, no checklist
|
|
359
|
+
- professional: + reviewer (3 agents), 1-2 tasks, optional checklist
|
|
360
|
+
- elite: + strategist (4 agents), 2-3 tasks, mandatory quality-gate checklist
|
|
361
|
+
- All ids must be lowercase a-z0-9 with hyphens
|
|
362
|
+
- Prefix ids with "${id}-" so they're unique within the squad`;
|
|
363
|
+
const userMessage = `Brief: ${trimmed}\nMode: ${mode}\nSquad id: ${id}`;
|
|
364
|
+
try {
|
|
365
|
+
const raw = await brain.think(systemPrompt, userMessage);
|
|
366
|
+
const parsed = extractFirstJson(raw);
|
|
367
|
+
if (!parsed || typeof parsed !== 'object')
|
|
368
|
+
throw new Error('no_json_in_response');
|
|
369
|
+
const p = parsed;
|
|
370
|
+
if (!Array.isArray(p.agents) || p.agents.length < 2)
|
|
371
|
+
throw new Error('missing_or_too_few_agents');
|
|
372
|
+
const proposal = {
|
|
373
|
+
id, name: id, description: trimmed,
|
|
374
|
+
version: '0.1.0',
|
|
375
|
+
author: 'squad-creator.designWithBrain',
|
|
376
|
+
agents: p.agents.map((a) => ({ id: a.id, name: a.id, role: a.role })),
|
|
377
|
+
tasks: Array.isArray(p.tasks) ? p.tasks : [{ id: `${id}-default`, ownerAgentId: p.agents[0].id, purpose: trimmed }],
|
|
378
|
+
checklists: Array.isArray(p.checklists) ? p.checklists : [],
|
|
379
|
+
};
|
|
380
|
+
return { ok: true, proposal, source: 'brain' };
|
|
381
|
+
}
|
|
382
|
+
catch {
|
|
383
|
+
// Any failure (network, parse, schema) → heuristic fallback. Never lossy.
|
|
384
|
+
const r = this.design(brief, opts);
|
|
385
|
+
return r.ok ? { ok: true, proposal: r.proposal, source: 'heuristic' } : r;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
// ─────────────────────────────────────────────────────────
|
|
389
|
+
// migrate — rewrite SQUAD.md frontmatter version atomically
|
|
390
|
+
// ─────────────────────────────────────────────────────────
|
|
391
|
+
migrate(squadId, fromVersion, toVersion) {
|
|
392
|
+
if (!this.isValidId(squadId))
|
|
393
|
+
return { ok: false, squadId, error: 'invalid_squad_id' };
|
|
394
|
+
if (!this.isValidVersion(fromVersion) || !this.isValidVersion(toVersion)) {
|
|
395
|
+
return { ok: false, squadId, error: 'invalid_version', detail: 'expect semver-like X.Y or X.Y.Z' };
|
|
396
|
+
}
|
|
397
|
+
const squadFile = path.join(this.catalogRoot, squadId, 'SQUAD.md');
|
|
398
|
+
if (!fs.existsSync(squadFile))
|
|
399
|
+
return { ok: false, squadId, error: 'squad_not_found' };
|
|
400
|
+
const content = fs.readFileSync(squadFile, 'utf-8');
|
|
401
|
+
const current = this.findSquadVersion(content);
|
|
402
|
+
if (current && current !== fromVersion) {
|
|
403
|
+
return { ok: false, squadId, error: 'version_mismatch', detail: `current=${current} expected=${fromVersion}` };
|
|
404
|
+
}
|
|
405
|
+
const next = this.rewriteSquadVersion(content, toVersion);
|
|
406
|
+
(0, AtomicWriter_1.writeStringAtomic)(squadFile, next);
|
|
407
|
+
return { ok: true, squadId, fromVersion, toVersion, filesChanged: [squadFile] };
|
|
408
|
+
}
|
|
409
|
+
findSquadVersion(content) {
|
|
410
|
+
const fm = this.parseFrontmatter(content);
|
|
411
|
+
if (fm.version)
|
|
412
|
+
return fm.version.replace(/^["']|["']$/g, '');
|
|
413
|
+
// Fall back to an embedded yaml `version: "X.Y.Z"` line.
|
|
414
|
+
const m = content.match(/^version:\s*"?([^\n"']+?)"?\s*$/m);
|
|
415
|
+
return m ? m[1] : null;
|
|
416
|
+
}
|
|
417
|
+
rewriteSquadVersion(content, toVersion) {
|
|
418
|
+
// 1. Replace the embedded yaml version line (if any), outside frontmatter.
|
|
419
|
+
const fmMatch = content.match(/^---\n[\s\S]*?\n---\n/);
|
|
420
|
+
const head = fmMatch ? content.slice(0, fmMatch[0].length) : '';
|
|
421
|
+
const body = fmMatch ? content.slice(fmMatch[0].length) : content;
|
|
422
|
+
const bodyReplaced = body.replace(/^version:\s*"?[^\n"']+"?\s*$/m, `version: "${toVersion}"`);
|
|
423
|
+
// 2. Update or insert the frontmatter version field.
|
|
424
|
+
return this.rewriteFrontmatterField(head + bodyReplaced, 'version', `"${toVersion}"`);
|
|
425
|
+
}
|
|
426
|
+
// ─────────────────────────────────────────────────────────
|
|
427
|
+
// extend — append component without re-rendering the whole squad
|
|
428
|
+
// ─────────────────────────────────────────────────────────
|
|
429
|
+
extend(squadId, component) {
|
|
430
|
+
if (!this.isValidId(squadId))
|
|
431
|
+
return { ok: false, squadId, error: 'invalid_squad_id' };
|
|
432
|
+
const squadDir = path.join(this.catalogRoot, squadId);
|
|
433
|
+
const squadFile = path.join(squadDir, 'SQUAD.md');
|
|
434
|
+
if (!fs.existsSync(squadFile))
|
|
435
|
+
return { ok: false, squadId, error: 'squad_not_found' };
|
|
436
|
+
const fm = this.parseFrontmatter(fs.readFileSync(squadFile, 'utf-8'));
|
|
437
|
+
const squadName = fm.name || squadId;
|
|
438
|
+
const filesAdded = [];
|
|
439
|
+
try {
|
|
440
|
+
if (component.kind === 'agent') {
|
|
441
|
+
fs.mkdirSync(path.join(squadDir, 'agents'), { recursive: true });
|
|
442
|
+
const proposalLike = { id: squadId, name: squadName, description: fm.description || '', agents: [component.spec] };
|
|
443
|
+
const vars = this.buildAgentVars(component.spec, proposalLike);
|
|
444
|
+
const out = (0, TemplateRenderer_1.renderTemplateFile)(path.join(this.templateRoot, 'agent-template.md'), vars);
|
|
445
|
+
const file = path.join(squadDir, 'agents', `${component.spec.id}.md`);
|
|
446
|
+
if (fs.existsSync(file))
|
|
447
|
+
return { ok: false, squadId, error: 'component_exists', detail: file };
|
|
448
|
+
(0, AtomicWriter_1.writeStringAtomic)(file, out);
|
|
449
|
+
filesAdded.push(file);
|
|
450
|
+
}
|
|
451
|
+
else if (component.kind === 'task') {
|
|
452
|
+
fs.mkdirSync(path.join(squadDir, 'tasks'), { recursive: true });
|
|
453
|
+
const proposalLike = { id: squadId, name: squadName, description: fm.description || '', agents: [] };
|
|
454
|
+
const vars = this.buildTaskVars(component.spec, proposalLike);
|
|
455
|
+
const out = (0, TemplateRenderer_1.renderTemplateFile)(path.join(this.templateRoot, 'task-template.md'), vars);
|
|
456
|
+
const file = path.join(squadDir, 'tasks', `${component.spec.id}.md`);
|
|
457
|
+
if (fs.existsSync(file))
|
|
458
|
+
return { ok: false, squadId, error: 'component_exists', detail: file };
|
|
459
|
+
(0, AtomicWriter_1.writeStringAtomic)(file, out);
|
|
460
|
+
filesAdded.push(file);
|
|
461
|
+
}
|
|
462
|
+
else if (component.kind === 'checklist') {
|
|
463
|
+
fs.mkdirSync(path.join(squadDir, 'checklists'), { recursive: true });
|
|
464
|
+
const ckVars = {
|
|
465
|
+
CHECKLIST_NAME: component.spec.id,
|
|
466
|
+
SQUAD_NAME: squadName,
|
|
467
|
+
PURPOSE: component.spec.purpose,
|
|
468
|
+
ITEM_1: component.spec.items[0] || '',
|
|
469
|
+
ITEM_2: component.spec.items[1] || '',
|
|
470
|
+
ITEM_3: component.spec.items[2] || '',
|
|
471
|
+
ITEM_4: component.spec.items[3] || '',
|
|
472
|
+
ITEM_5: component.spec.items[4] || '',
|
|
473
|
+
};
|
|
474
|
+
const out = (0, TemplateRenderer_1.renderTemplateFile)(path.join(this.templateRoot, 'checklist-template.md'), ckVars);
|
|
475
|
+
const file = path.join(squadDir, 'checklists', `${component.spec.id}.md`);
|
|
476
|
+
if (fs.existsSync(file))
|
|
477
|
+
return { ok: false, squadId, error: 'component_exists', detail: file };
|
|
478
|
+
(0, AtomicWriter_1.writeStringAtomic)(file, out);
|
|
479
|
+
filesAdded.push(file);
|
|
480
|
+
}
|
|
481
|
+
else if (component.kind === 'workflow') {
|
|
482
|
+
fs.mkdirSync(path.join(squadDir, 'workflows'), { recursive: true });
|
|
483
|
+
const file = path.join(squadDir, 'workflows', `${component.id}.yaml`);
|
|
484
|
+
if (fs.existsSync(file))
|
|
485
|
+
return { ok: false, squadId, error: 'component_exists', detail: file };
|
|
486
|
+
(0, AtomicWriter_1.writeStringAtomic)(file, component.content);
|
|
487
|
+
filesAdded.push(file);
|
|
488
|
+
}
|
|
489
|
+
else {
|
|
490
|
+
return { ok: false, squadId, error: 'unknown_component_kind' };
|
|
491
|
+
}
|
|
492
|
+
this.appendExtensionLog(squadFile, component.kind, filesAdded);
|
|
493
|
+
return { ok: true, squadId, kind: component.kind, filesAdded };
|
|
494
|
+
}
|
|
495
|
+
catch (err) {
|
|
496
|
+
return { ok: false, squadId, error: 'extend_failed', detail: err instanceof Error ? err.message : String(err) };
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
// ─────────────────────────────────────────────────────────
|
|
500
|
+
// publish — hash + ledger append + status flip to active
|
|
501
|
+
// ─────────────────────────────────────────────────────────
|
|
502
|
+
publish(squadId) {
|
|
503
|
+
if (!this.isValidId(squadId))
|
|
504
|
+
return { ok: false, squadId, error: 'invalid_squad_id' };
|
|
505
|
+
const squadFile = path.join(this.catalogRoot, squadId, 'SQUAD.md');
|
|
506
|
+
if (!fs.existsSync(squadFile))
|
|
507
|
+
return { ok: false, squadId, error: 'squad_not_found' };
|
|
508
|
+
let content = fs.readFileSync(squadFile, 'utf-8');
|
|
509
|
+
const fm = this.parseFrontmatter(content);
|
|
510
|
+
if (fm.status === 'archived') {
|
|
511
|
+
return { ok: false, squadId, error: 'squad_archived', detail: 'cannot publish archived squad' };
|
|
512
|
+
}
|
|
513
|
+
if (fm.status !== 'active') {
|
|
514
|
+
content = this.rewriteFrontmatterField(content, 'status', 'active');
|
|
515
|
+
(0, AtomicWriter_1.writeStringAtomic)(squadFile, content);
|
|
516
|
+
}
|
|
517
|
+
const sha256 = crypto.createHash('sha256').update(content).digest('hex');
|
|
518
|
+
const stateDir = process.env.OPENLIFE_STATE_DIR || path.join(process.cwd(), '.openlife');
|
|
519
|
+
fs.mkdirSync(stateDir, { recursive: true });
|
|
520
|
+
const ledger = path.join(stateDir, 'published-assets.jsonl');
|
|
521
|
+
const entry = JSON.stringify({ kind: 'squad', id: squadId, sha256, file: squadFile, publishedAt: new Date().toISOString() }) + '\n';
|
|
522
|
+
fs.appendFileSync(ledger, entry, 'utf-8');
|
|
523
|
+
return { ok: true, squadId, sha256, ledgerEntry: entry.trim(), status: 'active' };
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Story 14.1 (v1.5) — Publish + push to a remote registry.
|
|
527
|
+
*
|
|
528
|
+
* Composes the existing local-ledger publish with RemotePublisher.
|
|
529
|
+
* The local seal lands first (so callers still have a sealed asset
|
|
530
|
+
* if the remote push fails). The returned envelope carries both
|
|
531
|
+
* results: `ok` reflects the LOCAL publish; the remote outcome lives
|
|
532
|
+
* on the `remote` field so failures can be inspected without
|
|
533
|
+
* losing the local seal.
|
|
534
|
+
*
|
|
535
|
+
* Refuses to attempt the remote push if RemotePublisher is not
|
|
536
|
+
* configured — returns `remote.error='remote_publish_not_configured'`.
|
|
537
|
+
*/
|
|
538
|
+
async publishWithRemote(squadId) {
|
|
539
|
+
const local = this.publish(squadId);
|
|
540
|
+
if (!local.ok || !local.sha256)
|
|
541
|
+
return local;
|
|
542
|
+
const squadFile = path.join(this.catalogRoot, squadId, 'SQUAD.md');
|
|
543
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
544
|
+
const { RemotePublisher } = require('./RemotePublisher');
|
|
545
|
+
const remote = await new RemotePublisher().publishAsset('squad', squadId, local.sha256, squadFile);
|
|
546
|
+
return { ...local, remote };
|
|
547
|
+
}
|
|
548
|
+
// ─────────────────────────────────────────────────────────
|
|
549
|
+
// Helpers — used by migrate/extend/publish
|
|
550
|
+
// ─────────────────────────────────────────────────────────
|
|
551
|
+
isValidVersion(v) {
|
|
552
|
+
return /^\d+\.\d+(\.\d+)?$/.test(v);
|
|
553
|
+
}
|
|
554
|
+
rewriteFrontmatterField(content, field, value) {
|
|
555
|
+
const fmMatch = content.match(/^---\n([\s\S]*?)\n---\n/);
|
|
556
|
+
if (!fmMatch) {
|
|
557
|
+
// No frontmatter — prepend one.
|
|
558
|
+
return `---\n${field}: ${value}\n---\n${content}`;
|
|
559
|
+
}
|
|
560
|
+
const block = fmMatch[1];
|
|
561
|
+
const lines = block.split('\n');
|
|
562
|
+
const idx = lines.findIndex((l) => l.match(new RegExp(`^${field}:`)));
|
|
563
|
+
if (idx >= 0)
|
|
564
|
+
lines[idx] = `${field}: ${value}`;
|
|
565
|
+
else
|
|
566
|
+
lines.push(`${field}: ${value}`);
|
|
567
|
+
const newBlock = lines.join('\n');
|
|
568
|
+
return content.replace(fmMatch[0], `---\n${newBlock}\n---\n`);
|
|
569
|
+
}
|
|
570
|
+
appendExtensionLog(squadFile, kind, files) {
|
|
571
|
+
try {
|
|
572
|
+
const marker = '\n## Extensions\n';
|
|
573
|
+
let content = fs.readFileSync(squadFile, 'utf-8');
|
|
574
|
+
if (!content.includes('## Extensions'))
|
|
575
|
+
content += marker;
|
|
576
|
+
const ts = new Date().toISOString();
|
|
577
|
+
content += `- ${ts} added ${kind}: ${files.map((f) => path.basename(f)).join(', ')}\n`;
|
|
578
|
+
(0, AtomicWriter_1.writeStringAtomic)(squadFile, content);
|
|
579
|
+
}
|
|
580
|
+
catch { /* extension log is best-effort */ }
|
|
581
|
+
}
|
|
582
|
+
// ─────────────────────────────────────────────────────────
|
|
583
|
+
// Internals
|
|
584
|
+
// ─────────────────────────────────────────────────────────
|
|
585
|
+
buildSquadVars(p) {
|
|
586
|
+
const agentRows = p.agents.map((a) => `| ${a.id} | ${a.role} |`).join('\n');
|
|
587
|
+
const tagsList = (p.tags || []).map((t) => ` - ${t}`).join('\n');
|
|
588
|
+
const componentAgents = p.agents.map((a) => ` - ${a.id}.md`).join('\n');
|
|
589
|
+
const componentTasks = (p.tasks || []).map((t) => ` - ${t.id}.md`).join('\n');
|
|
590
|
+
const componentWorkflows = (p.workflows || []).map((w) => ` - ${w}`).join('\n');
|
|
591
|
+
const componentChecklists = (p.checklists || []).map((c) => ` - ${c.id}.md`).join('\n');
|
|
592
|
+
return {
|
|
593
|
+
SQUAD_ID: p.id,
|
|
594
|
+
SQUAD_NAME: p.name,
|
|
595
|
+
SOURCE: p.source || 'aiobuilder-built',
|
|
596
|
+
STATUS: p.status || 'draft',
|
|
597
|
+
DESCRIPTION: p.description,
|
|
598
|
+
VERSION: p.version || '1.0.0',
|
|
599
|
+
SHORT_TITLE: p.shortTitle || p.name,
|
|
600
|
+
AUTHOR: p.author || 'OpenLife',
|
|
601
|
+
LICENSE: p.license || 'MIT',
|
|
602
|
+
SLASH_PREFIX: p.slashPrefix || p.id,
|
|
603
|
+
TAGS_LIST: tagsList || ' - squad',
|
|
604
|
+
AGENTS_TABLE: agentRows,
|
|
605
|
+
COMPONENT_AGENTS: componentAgents,
|
|
606
|
+
COMPONENT_TASKS: componentTasks || ' []',
|
|
607
|
+
COMPONENT_WORKFLOWS: componentWorkflows || ' []',
|
|
608
|
+
COMPONENT_CHECKLISTS: componentChecklists || ' []',
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
buildAgentVars(a, p) {
|
|
612
|
+
const base = {
|
|
613
|
+
AGENT_ID: a.id,
|
|
614
|
+
AGENT_NAME: a.name,
|
|
615
|
+
AGENT_ROLE: a.role,
|
|
616
|
+
AGENT_TITLE: a.name,
|
|
617
|
+
SQUAD_NAME: p.name,
|
|
618
|
+
ICON: '🤖',
|
|
619
|
+
WHEN_TO_USE: `when ${a.role} is needed`,
|
|
620
|
+
DESCRIPTION: `${a.name} — ${a.role}`,
|
|
621
|
+
ROLE: a.role,
|
|
622
|
+
STYLE: 'analytical',
|
|
623
|
+
IDENTITY: `Specialist in ${a.role}`,
|
|
624
|
+
FOCUS: a.role,
|
|
625
|
+
PRINCIPLE_1: 'Respect tool permissions',
|
|
626
|
+
PRINCIPLE_2: 'Emit audit events',
|
|
627
|
+
PRINCIPLE_3: 'Defer to human approval beyond autonomy budget',
|
|
628
|
+
COMMAND_1: 'execute',
|
|
629
|
+
COMMAND_1_DESC: 'Execute the canonical task for this role',
|
|
630
|
+
CAPABILITIES_LIST: '- TBD',
|
|
631
|
+
};
|
|
632
|
+
return { ...base, ...(a.vars || {}) };
|
|
633
|
+
}
|
|
634
|
+
buildTaskVars(t, p) {
|
|
635
|
+
const base = {
|
|
636
|
+
TASK_NAME: t.id,
|
|
637
|
+
OWNER_AGENT: t.ownerAgentId,
|
|
638
|
+
SQUAD_NAME: p.name,
|
|
639
|
+
PURPOSE: t.purpose,
|
|
640
|
+
INPUT_1: 'TBD',
|
|
641
|
+
INPUT_2: 'TBD',
|
|
642
|
+
OUTPUT_1: 'TBD',
|
|
643
|
+
OUTPUT_2: 'TBD',
|
|
644
|
+
STEP_1: 'Load context',
|
|
645
|
+
STEP_2: 'Execute primary action',
|
|
646
|
+
STEP_3: 'Record evidence and exit',
|
|
647
|
+
VALIDATION_1: 'Output exists and is non-empty',
|
|
648
|
+
VALIDATION_2: 'Audit event emitted',
|
|
649
|
+
NOTES: '',
|
|
650
|
+
};
|
|
651
|
+
return { ...base, ...(t.vars || {}) };
|
|
652
|
+
}
|
|
653
|
+
parseFrontmatter(content) {
|
|
654
|
+
const match = content.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
655
|
+
if (!match)
|
|
656
|
+
return {};
|
|
657
|
+
const out = {};
|
|
658
|
+
for (const line of match[1].split('\n')) {
|
|
659
|
+
const m = line.match(/^([\w-]+):\s*(.*)$/);
|
|
660
|
+
if (m)
|
|
661
|
+
out[m[1]] = m[2].trim();
|
|
662
|
+
}
|
|
663
|
+
return out;
|
|
664
|
+
}
|
|
665
|
+
parseComponentRefs(content) {
|
|
666
|
+
// Look for the embedded squad.yaml block; parse components: lists.
|
|
667
|
+
const out = { agents: [], tasks: [], workflows: [], checklists: [] };
|
|
668
|
+
const section = content.match(/components:\s*\n([\s\S]*?)(?=\nconfig:|\n```|$)/);
|
|
669
|
+
if (!section)
|
|
670
|
+
return out;
|
|
671
|
+
let current = null;
|
|
672
|
+
for (const line of section[1].split('\n')) {
|
|
673
|
+
const keyMatch = line.match(/^\s\s(agents|tasks|workflows|checklists):/);
|
|
674
|
+
if (keyMatch) {
|
|
675
|
+
current = keyMatch[1];
|
|
676
|
+
continue;
|
|
677
|
+
}
|
|
678
|
+
const itemMatch = line.match(/^\s\s\s\s-\s+(.+)$/);
|
|
679
|
+
if (itemMatch && current) {
|
|
680
|
+
const v = itemMatch[1].trim();
|
|
681
|
+
if (v && v !== '[]')
|
|
682
|
+
out[current].push(v);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
return out;
|
|
686
|
+
}
|
|
687
|
+
isValidId(id) {
|
|
688
|
+
return /^[a-z0-9][a-z0-9._-]+$/i.test(id);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
exports.SquadCreator = SquadCreator;
|
|
692
|
+
function slugifyForId(s) {
|
|
693
|
+
return s.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Story 8.1 helper — extract the first JSON object from raw model output.
|
|
697
|
+
* Tolerates markdown fences (```json ... ```) and surrounding prose.
|
|
698
|
+
* Returns the parsed object or null. Never throws.
|
|
699
|
+
*/
|
|
700
|
+
function extractFirstJson(raw) {
|
|
701
|
+
if (typeof raw !== 'string')
|
|
702
|
+
return null;
|
|
703
|
+
// Strip common markdown fences first.
|
|
704
|
+
let s = raw.replace(/```(?:json)?\s*\n?/gi, '').replace(/```/g, '');
|
|
705
|
+
const start = s.indexOf('{');
|
|
706
|
+
if (start === -1)
|
|
707
|
+
return null;
|
|
708
|
+
// Find matching closing brace by depth counting (handles nested).
|
|
709
|
+
let depth = 0;
|
|
710
|
+
for (let i = start; i < s.length; i++) {
|
|
711
|
+
const c = s[i];
|
|
712
|
+
if (c === '{')
|
|
713
|
+
depth++;
|
|
714
|
+
else if (c === '}') {
|
|
715
|
+
depth--;
|
|
716
|
+
if (depth === 0) {
|
|
717
|
+
try {
|
|
718
|
+
return JSON.parse(s.slice(start, i + 1));
|
|
719
|
+
}
|
|
720
|
+
catch {
|
|
721
|
+
return null;
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
return null;
|
|
727
|
+
}
|