thinkwork-cli 0.12.12 → 0.12.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -38
- package/dist/{api-client-ASJWIG3Z.js → api-client-4VT3FWOH.js} +1 -1
- package/dist/{chunk-JEHV35EU.js → chunk-H7AB42ES.js} +340 -30
- package/dist/cli.js +22123 -9219
- package/dist/commands/enterprise/templates/deploy-repo/.github/workflows/deploy.yml +17 -6
- package/dist/commands/enterprise/templates/deploy-repo/docs/runbook.md +81 -9
- package/dist/commands/enterprise/templates/deploy-repo/scripts/apply-release.mjs +183 -21
- package/dist/commands/enterprise/templates/deploy-repo/scripts/smoke.mjs +1 -2
- package/dist/commands/enterprise/templates/deploy-repo/terraform/main.tf +286 -0
- package/dist/drizzle/0000_petite_marvel_apes.sql +1188 -0
- package/dist/drizzle/0001_milky_surge.sql +103 -0
- package/dist/drizzle/0002_aberrant_mattie_franklin.sql +41 -0
- package/dist/drizzle/0003_exotic_ogun.sql +3 -0
- package/dist/drizzle/0004_glamorous_magneto.sql +2 -0
- package/dist/drizzle/0005_ancient_nocturne.sql +2 -0
- package/dist/drizzle/0006_new_colonel_america.sql +2 -0
- package/dist/drizzle/0007_unique_external_task_id.sql +31 -0
- package/dist/drizzle/0008_external_task_id_column.sql +43 -0
- package/dist/drizzle/0009_evaluations.sql +64 -0
- package/dist/drizzle/0010_eval_agent_template.sql +2 -0
- package/dist/drizzle/0011_eval_run_template.sql +2 -0
- package/dist/drizzle/0012_eval_seed_unique.sql +16 -0
- package/dist/drizzle/0013_motionless_white_queen.sql +140 -0
- package/dist/drizzle/0014_wiki_aggregation.sql +11 -0
- package/dist/drizzle/0015_pg_trgm_alias_title_indexes.sql +9 -0
- package/dist/drizzle/0016_wiki_schema_drops.sql +24 -0
- package/dist/drizzle/0017_wiki_places.sql +46 -0
- package/dist/drizzle/0018_agent_workspace_overlay.sql +26 -0
- package/dist/drizzle/0018_cool_firebird.sql +24 -0
- package/dist/drizzle/0018_skill_runs.sql +84 -0
- package/dist/drizzle/0019_condemned_dragon_man.sql +95 -0
- package/dist/drizzle/0019_webhook_skill_runs.sql +58 -0
- package/dist/drizzle/0020_crazy_the_anarchist.sql +9 -0
- package/dist/drizzle/0020_mutation_idempotency.sql +64 -0
- package/dist/drizzle/0021_crm_work_links.sql +76 -0
- package/dist/drizzle/0021_skill_runs_completion_hmac.sql +33 -0
- package/dist/drizzle/0022_add_billing_tables.sql +70 -0
- package/dist/drizzle/0022_seed_thinkwork_admin_permissions.sql +160 -0
- package/dist/drizzle/0022_stripe_billing_indexes.sql +48 -0
- package/dist/drizzle/0023_tenants_deactivation.sql +27 -0
- package/dist/drizzle/0024_tenant_mcp_admin_keys.sql +63 -0
- package/dist/drizzle/0025_v1_agent_architecture.sql +182 -0
- package/dist/drizzle/0026_thread_turns_add_kind.sql +28 -0
- package/dist/drizzle/0027_capability_catalog_and_manifests.sql +219 -0
- package/dist/drizzle/0028_aws_s3_extension.sql +26 -0
- package/dist/drizzle/0029_collapse_execution_types.sql +42 -0
- package/dist/drizzle/0030_retire_composition_primitives.sql +48 -0
- package/dist/drizzle/0031_thread_cleanup_drops.sql +131 -0
- package/dist/drizzle/0032_add_browser_automation_template_config.sql +24 -0
- package/dist/drizzle/0033_agent_operation_leases.sql +32 -0
- package/dist/drizzle/0034_agent_workspace_events.sql +116 -0
- package/dist/drizzle/0035_workspace_review_decisions.sql +19 -0
- package/dist/drizzle/0036_user_scoped_memory_wiki.sql +152 -0
- package/dist/drizzle/0037_user_profile_operating_model.sql +34 -0
- package/dist/drizzle/0042_agent_runtime_selector.sql +41 -0
- package/dist/drizzle/0043_wiki_search_tsv_tokenization.sql +48 -0
- package/dist/drizzle/0044_add_web_search_template_config.sql +7 -0
- package/dist/drizzle/0045_add_send_email_template_config.sql +7 -0
- package/dist/drizzle/0046_tenant_mcp_context_tools.sql +26 -0
- package/dist/drizzle/0047_add_context_engine_template_config.sql +7 -0
- package/dist/drizzle/0048_tenant_context_provider_settings.sql +22 -0
- package/dist/drizzle/0050_brain_v0_entity_subtype.sql +29 -0
- package/dist/drizzle/0051_brain_v0_tenant_entity_pages.sql +170 -0
- package/dist/drizzle/0052_brain_v0_external_refs.sql +41 -0
- package/dist/drizzle/0053_brain_v0_unresolved_mentions_subtype.sql +48 -0
- package/dist/drizzle/0054_wiki_compile_jobs_input.sql +28 -0
- package/dist/drizzle/0055_routines_stepfunctions_substrate.sql +202 -0
- package/dist/drizzle/0056_routine_step_events_dedup_idx.sql +25 -0
- package/dist/drizzle/0057_archive_legacy_python_routines.sql +36 -0
- package/dist/drizzle/0058_routines_visibility_and_owning_agent.sql +61 -0
- package/dist/drizzle/0061_routine_execution_asl_version_id.sql +29 -0
- package/dist/drizzle/0062_migrate_pi_to_flue.sql +110 -0
- package/dist/drizzle/0063_threads_session_data.sql +23 -0
- package/dist/drizzle/0064_tenant_credentials.sql +65 -0
- package/dist/drizzle/0067_thinkwork_computers_phase_one.sql +174 -0
- package/dist/drizzle/0068_drop_system_workflows_and_activation.sql +115 -0
- package/dist/drizzle/0069_compliance_schema.sql +364 -0
- package/dist/drizzle/0070_compliance_aurora_roles.sql +171 -0
- package/dist/drizzle/0072_threads_computer_ownership.sql +24 -0
- package/dist/drizzle/0073_compliance_tenant_anchor_state.sql +105 -0
- package/dist/drizzle/0074_compliance_event_hash_index.sql +49 -0
- package/dist/drizzle/0075_scheduled_jobs_computer_ownership.sql +27 -0
- package/dist/drizzle/0076_scheduled_jobs_marco_backfill.sql +69 -0
- package/dist/drizzle/0077_computers_primary_agent_id.sql +38 -0
- package/dist/drizzle/0078_tenant_customize_catalog.sql +46 -0
- package/dist/drizzle/0079_seed_tenant_customize_catalog.sql +53 -0
- package/dist/drizzle/0081_routines_catalog_slug.sql +42 -0
- package/dist/drizzle/0082_messages_parts_jsonb.sql +52 -0
- package/dist/drizzle/0083_computer_runbooks.sql +103 -0
- package/dist/drizzle/0084_artifacts_favorited_at.sql +32 -0
- package/dist/drizzle/0085_seed_thinkwork_computer_default_template.sql +46 -0
- package/dist/drizzle/0086_update_default_template_models.sql +19 -0
- package/dist/drizzle/0087_retire_oss_connectors.sql +77 -0
- package/dist/drizzle/0088_compliance_event_types_finance_pilot.sql +37 -0
- package/dist/drizzle/0089_remove_maniflow_eval_seeds.sql +81 -0
- package/dist/drizzle/0089_wiki_schema_extraction.sql +315 -0
- package/dist/drizzle/0090_brain_schema_extraction.sql +274 -0
- package/dist/drizzle/0091_drop_wiki_brain_compat_views.sql +118 -0
- package/dist/drizzle/0092_finish_oss_connector_retirement.sql +97 -0
- package/dist/drizzle/0093_eval_runs_scheduled_job_id.sql +23 -0
- package/dist/drizzle/0094_slack_workspace_app.sql +140 -0
- package/dist/drizzle/0095_eval_runs_computer_id.sql +22 -0
- package/dist/drizzle/0096_true_redteam_eval_seed_cleanup.sql +67 -0
- package/dist/drizzle/0097_shared_computers.sql +191 -0
- package/dist/drizzle/0098_business_ontology.sql +587 -0
- package/dist/drizzle/0099_thread_idle_learning.sql +145 -0
- package/dist/drizzle/0100_ontology_gated_brain_dynamic_subtypes.sql +7 -0
- package/dist/drizzle/0101_ontology_gated_unresolved_mentions_dynamic_subtypes.sql +7 -0
- package/dist/drizzle/0102_wiki_brain_owner_repair.sql +87 -0
- package/dist/drizzle/0103_brain_section_source_trigger_repair.sql +75 -0
- package/dist/drizzle/0104_expand_seed_ontology_for_user_memory.sql +166 -0
- package/dist/drizzle/0105_spaces_domain.sql +349 -0
- package/dist/drizzle/0106_expand_seed_ontology_for_triples.sql +173 -0
- package/dist/drizzle/0106_space_threads_participants.sql +197 -0
- package/dist/drizzle/0107_linked_task_mirror.sql +245 -0
- package/dist/drizzle/0108_message_mentions.sql +95 -0
- package/dist/drizzle/0109_space_owned_threads.sql +217 -0
- package/dist/drizzle/0110_thread_participant_read_state.sql +21 -0
- package/dist/drizzle/0111_agents_tenant_name_unique.sql +38 -0
- package/dist/drizzle/0112_recast_spaces_as_contextual_workrooms.sql +138 -0
- package/dist/drizzle/0113_agents_own_runtime_fields.sql +48 -0
- package/dist/drizzle/0114_migrate_templates_to_agents_and_spaces.sql +361 -0
- package/dist/drizzle/0115_remove_computer_eval_template_dependencies.sql +69 -0
- package/dist/drizzle/0116_backfill_thread_participants_access.sql +126 -0
- package/dist/drizzle/0117_space_access_mode.sql +40 -0
- package/dist/drizzle/0118_normalize_tenant_member_principal_type.sql +41 -0
- package/dist/drizzle/0119_space_knowledge_bases.sql +126 -0
- package/dist/drizzle/0120_space_automations.sql +58 -0
- package/dist/drizzle/0121_drop_computer_runbooks.sql +26 -0
- package/dist/drizzle/0122_space_email_triggers.sql +24 -0
- package/dist/drizzle/0123_single_platform_agent_and_overrides.sql +58 -0
- package/dist/drizzle/0124_thread_turns_finalized_at.sql +17 -0
- package/dist/drizzle/0125_drop_space_agent_assignments.sql +42 -0
- package/dist/drizzle/0126_migrate_flue_to_pi.sql +42 -0
- package/dist/drizzle/0127_runtime_type_observability.sql +28 -0
- package/dist/drizzle/0128_drop_eval_test_cases_agent_id.sql +37 -0
- package/dist/drizzle/0129_drop_code_factory_tables.sql +43 -0
- package/dist/drizzle/0129_eval_results_system_prompt.sql +30 -0
- package/dist/drizzle/0130_drop_teams_and_team_id_columns.sql +54 -0
- package/dist/drizzle/0131_drop_skill_catalog_and_tenant_skills.sql +33 -0
- package/dist/drizzle/0132_drop_computer_tables.sql +31 -0
- package/dist/drizzle/0133_drop_seed_view_tombstones.sql +79 -0
- package/dist/drizzle/0133_thread_turns_system_prompt.sql +22 -0
- package/dist/drizzle/0134_drop_dead_agent_runtime_tables.sql +53 -0
- package/dist/drizzle/0135_native_checklist_linked_tasks.sql +48 -0
- package/dist/drizzle/0136_goal_ledger.sql +55 -0
- package/dist/drizzle/0136_space_email_trigger_status.sql +66 -0
- package/dist/drizzle/0137_thread_participant_pins.sql +25 -0
- package/dist/drizzle/0138_users_cognito_sub.sql +30 -0
- package/dist/drizzle/0139_mobile_turn_client_id.sql +23 -0
- package/dist/drizzle/0140_workspace_folder_names.sql +36 -0
- package/dist/drizzle/0141_eval_runs_desktop_pi_provenance.sql +31 -0
- package/dist/drizzle/0142_pi_only_agent_runtime.sql +61 -0
- package/dist/drizzle/0143_add_web_extract_builtin_config.sql +28 -0
- package/dist/drizzle/0144_skill_catalog_index.sql +40 -0
- package/dist/drizzle/0145_knowledge_graph_thread_ingest.sql +512 -0
- package/dist/drizzle/0146_knowledge_graph_source_scope.sql +413 -0
- package/dist/drizzle/0147_knowledge_graph_scope_trigger_qualification.sql +211 -0
- package/dist/drizzle/0148_user_cost_attribution.sql +171 -0
- package/dist/drizzle/0149_managed_mcp_servers.sql +64 -0
- package/dist/drizzle/0149_user_model_approvals.sql +89 -0
- package/dist/drizzle/0150_bootstrap_first_admin_claim.sql +11 -0
- package/dist/drizzle/0151_managed_deployments.sql +91 -0
- package/dist/drizzle/0152_agent_profiles.sql +81 -0
- package/dist/drizzle/0153_message_mentions_agent_profile.sql +9 -0
- package/dist/drizzle/0154_customer_deployment_sessions.sql +61 -0
- package/dist/drizzle/0155_tenant_model_catalog.sql +152 -0
- package/dist/drizzle/0156_bootstrap_credential_leases.sql +52 -0
- package/dist/drizzle/0157_knowledge_graph_observations_source.sql +79 -0
- package/dist/drizzle/0158_pending_user_questions.sql +80 -0
- package/dist/drizzle/0158_wiki_tenant_scope.sql +38 -0
- package/dist/drizzle/0159_agent_profiles_space_local.sql +49 -0
- package/dist/drizzle/0159_application_plugins.sql +193 -0
- package/dist/drizzle/0159_eval_scoring_version.sql +46 -0
- package/dist/drizzle/0160_compliance_event_types_plugins.sql +37 -0
- package/dist/drizzle/0160_eval_datasets.sql +75 -0
- package/dist/drizzle/0160_eval_results_thread_turn.sql +35 -0
- package/dist/drizzle/0161_eval_run_pinning.sql +54 -0
- package/dist/drizzle/0161_tenant_mcp_servers_plugin_source.sql +62 -0
- package/dist/drizzle/0162_eval_result_override.sql +49 -0
- package/dist/drizzle/0163_eval_replay_tool_allowlist.sql +50 -0
- package/dist/drizzle/0164_eval_replay_tool_override_mode.sql +52 -0
- package/dist/drizzle/0165_plugin_premium_entitlements.sql +153 -0
- package/dist/drizzle/0166_company_brain_substrate_contract.sql +386 -0
- package/dist/drizzle/0167_company_brain_artifact_manifest_runtime.sql +85 -0
- package/dist/drizzle/0168_eval_skill_gate.sql +44 -0
- package/dist/drizzle/0169_release_update_jobs.sql +67 -0
- package/dist/drizzle/0170_email_channel_plugin.sql +291 -0
- package/dist/drizzle/0171_linked_task_external_providers.sql +30 -0
- package/dist/drizzle/0172_email_channel_sendgrid_provider.sql +8 -0
- package/dist/drizzle/0173_auth_provider_resources.sql +83 -0
- package/dist/drizzle/0174_workos_auth_bridges.sql +37 -0
- package/dist/drizzle/0175_workos_auth_sessions.sql +38 -0
- package/dist/drizzle/0176_n8n_agent_step_runs.sql +107 -0
- package/dist/drizzle/0177_workflow_control_plane.sql +281 -0
- package/dist/drizzle/0178_workflow_backfill_existing_routines.sql +525 -0
- package/dist/drizzle/0179_tenant_goal_budget_settings.sql +21 -0
- package/dist/drizzle/0180_skill_drafts.sql +89 -0
- package/dist/drizzle/0181_skill_draft_publish_status.sql +21 -0
- package/dist/drizzle/0182_agent_loops.sql +260 -0
- package/dist/drizzle/0183_okf_artifact_manifests.sql +52 -0
- package/dist/drizzle/0184_skill_catalog_trust_cache.sql +35 -0
- package/dist/drizzle/0185_agent_loop_space.sql +16 -0
- package/dist/drizzle/0186_tenant_mcp_runtime_metadata.sql +10 -0
- package/dist/drizzle/0187_native_work_items.sql +220 -0
- package/dist/drizzle/0188_company_etl_plugin_rename.sql +99 -0
- package/dist/drizzle/0189_trace_cost_substrate.sql +247 -0
- package/dist/drizzle/0190_billing_export_reconciliation.sql +101 -0
- package/dist/drizzle/0191_open_engine_work_item_queue.sql +66 -0
- package/dist/drizzle/0192_work_item_labels.sql +44 -0
- package/dist/drizzle/0193_work_item_documents.sql +31 -0
- package/dist/drizzle/0194_memory_retain_attempts.sql +103 -0
- package/dist/drizzle/0194_work_item_comments.sql +35 -0
- package/dist/drizzle/0195_plugin_app_overlays.sql +45 -0
- package/dist/drizzle/0196_pi_extensions.sql +118 -0
- package/dist/drizzle/0197_eval_profiles.sql +150 -0
- package/dist/drizzle/0198_eval_runs_unstable.sql +27 -0
- package/dist/drizzle/0199_eval_runs_cost_partial.sql +27 -0
- package/dist/drizzle/meta/_journal.json +160 -0
- package/dist/plugins/README.md +162 -0
- package/dist/plugins/catalog/package.json +40 -0
- package/dist/plugins/catalog/scripts/build-catalog.ts +120 -0
- package/dist/plugins/catalog/scripts/generate-plugin-registry.ts +144 -0
- package/dist/plugins/catalog/src/__tests__/build-catalog.test.ts +86 -0
- package/dist/plugins/catalog/src/__tests__/catalog.test.ts +308 -0
- package/dist/plugins/catalog/src/__tests__/contracts.test.ts +861 -0
- package/dist/plugins/catalog/src/__tests__/plugin-package.test.ts +124 -0
- package/dist/plugins/catalog/src/__tests__/plugin-registry.test.ts +120 -0
- package/dist/plugins/catalog/src/catalog.ts +434 -0
- package/dist/plugins/catalog/src/contracts.ts +1498 -0
- package/dist/plugins/catalog/src/index.ts +4 -0
- package/dist/plugins/catalog/src/plugin-package.ts +171 -0
- package/dist/plugins/catalog/src/registry/generated-first-party.ts +76 -0
- package/dist/plugins/catalog/src/registry/index.ts +1 -0
- package/dist/plugins/catalog/tsconfig.json +8 -0
- package/dist/plugins/company-brain/README.md +37 -0
- package/dist/plugins/company-brain/package.json +33 -0
- package/dist/plugins/company-brain/runtime/cognee/Dockerfile +11 -0
- package/dist/plugins/company-brain/smoke/cognee-managed-app-smoke.mjs +458 -0
- package/dist/plugins/company-brain/smoke/cognee-memory-cutover-smoke.mjs +539 -0
- package/dist/plugins/company-brain/smoke/company-brain-context-engine-smoke.mjs +312 -0
- package/dist/plugins/company-brain/smoke/company-brain-operations-smoke.mjs +449 -0
- package/dist/plugins/company-brain/smoke/company-brain-plugin-smoke.mjs +655 -0
- package/dist/plugins/company-brain/smoke/hindsight-memory-isolation-smoke.mjs +474 -0
- package/dist/plugins/company-brain/src/api/cognee-client.ts +896 -0
- package/dist/plugins/company-brain/src/api/cognee-cluster-identity.ts +49 -0
- package/dist/plugins/company-brain/src/api/cognee-memory-scope.ts +84 -0
- package/dist/plugins/company-brain/src/api/context-engine-provider.ts +888 -0
- package/dist/plugins/company-brain/src/api/migration.ts +665 -0
- package/dist/plugins/company-brain/src/deployment/cognee-managed-app.ts +396 -0
- package/dist/plugins/company-brain/src/index.ts +62 -0
- package/dist/plugins/company-brain/src/manifest.ts +112 -0
- package/dist/plugins/company-brain/terraform/cognee/README.md +263 -0
- package/dist/plugins/company-brain/terraform/cognee/main.tf +766 -0
- package/dist/plugins/company-brain/terraform/cognee/outputs.tf +144 -0
- package/dist/plugins/company-brain/terraform/cognee/variables.tf +402 -0
- package/dist/plugins/company-brain/test/api/cognee-client.test.ts +562 -0
- package/dist/plugins/company-brain/test/api/cognee-memory-scope.test.ts +74 -0
- package/dist/plugins/company-brain/test/api/context-engine-provider.test.ts +447 -0
- package/dist/plugins/company-brain/test/api/migration.test.ts +465 -0
- package/dist/plugins/company-brain/test/manifest.test.ts +95 -0
- package/dist/plugins/company-brain/tsconfig.json +8 -0
- package/dist/plugins/company-data/README.md +49 -0
- package/dist/plugins/company-data/package.json +21 -0
- package/dist/plugins/company-data/src/index.ts +30 -0
- package/dist/plugins/company-data/src/manifest.ts +26 -0
- package/dist/plugins/company-data/test/manifest.test.ts +116 -0
- package/dist/plugins/company-data/tsconfig.json +8 -0
- package/dist/plugins/company-etl/README.md +46 -0
- package/dist/plugins/company-etl/package.json +21 -0
- package/dist/plugins/company-etl/src/index.ts +29 -0
- package/dist/plugins/company-etl/src/manifest.ts +26 -0
- package/dist/plugins/company-etl/test/manifest.test.ts +108 -0
- package/dist/plugins/company-etl/tsconfig.json +8 -0
- package/dist/plugins/email-channel/README.md +41 -0
- package/dist/plugins/email-channel/package.json +22 -0
- package/dist/plugins/email-channel/src/index.ts +36 -0
- package/dist/plugins/email-channel/src/manifest.ts +44 -0
- package/dist/plugins/email-channel/src/provider-contract.ts +36 -0
- package/dist/plugins/email-channel/test/manifest.test.ts +119 -0
- package/dist/plugins/email-channel/tsconfig.json +8 -0
- package/dist/plugins/lastmile/README.md +50 -0
- package/dist/plugins/lastmile/package.json +23 -0
- package/dist/plugins/lastmile/smoke/lastmile-plugin-smoke.mjs +661 -0
- package/dist/plugins/lastmile/src/api/tasks-adapter.ts +572 -0
- package/dist/plugins/lastmile/src/discovery.fixture.ts +64 -0
- package/dist/plugins/lastmile/src/index.ts +46 -0
- package/dist/plugins/lastmile/src/manifest.ts +139 -0
- package/dist/plugins/lastmile/test/api/tasks-adapter.test.ts +266 -0
- package/dist/plugins/lastmile/test/discovery.test.ts +88 -0
- package/dist/plugins/lastmile/tsconfig.json +8 -0
- package/dist/plugins/n8n/README.md +201 -0
- package/dist/plugins/n8n/n8n-app/README.md +33 -0
- package/dist/plugins/n8n/n8n-app/package.json +23 -0
- package/dist/plugins/n8n/n8n-app/src/application-config.ts +45 -0
- package/dist/plugins/n8n/n8n-app/src/front-components/thinkwork-workflows.front-component.tsx +970 -0
- package/dist/plugins/n8n/n8n-app/src/lib/n8n-app-data.ts +190 -0
- package/dist/plugins/n8n/n8n-app/src/vite-env.d.ts +7 -0
- package/dist/plugins/n8n/n8n-app/tsconfig.json +15 -0
- package/dist/plugins/n8n/package.json +35 -0
- package/dist/plugins/n8n/runtime/Dockerfile +18 -0
- package/dist/plugins/n8n/runtime/n8n-task-runners.json +14 -0
- package/dist/plugins/n8n/runtime/n8n-task-runners.json.template +14 -0
- package/dist/plugins/n8n/runtime/package-lock.json +13 -0
- package/dist/plugins/n8n/runtime/package.json +7 -0
- package/dist/plugins/n8n/scripts/sync-thinkwork-app.mjs +268 -0
- package/dist/plugins/n8n/smoke/README.md +95 -0
- package/dist/plugins/n8n/smoke/n8n-agent-step-bridge-smoke.mjs +866 -0
- package/dist/plugins/n8n/smoke/n8n-integrated-app-smoke.mjs +262 -0
- package/dist/plugins/n8n/smoke/n8n-managed-app-smoke.mjs +409 -0
- package/dist/plugins/n8n/smoke/n8n-mcp-smoke.mjs +624 -0
- package/dist/plugins/n8n/src/deployment/image-build.ts +172 -0
- package/dist/plugins/n8n/src/deployment/managed-app.ts +429 -0
- package/dist/plugins/n8n/src/index.ts +79 -0
- package/dist/plugins/n8n/src/manifest.ts +403 -0
- package/dist/plugins/n8n/src/package-config.ts +186 -0
- package/dist/plugins/n8n/src/skills/n8n-workflow-operator/SKILL.md +76 -0
- package/dist/plugins/n8n/src/skills/n8n-workflow-operator/references/mcp-tooling.md +40 -0
- package/dist/plugins/n8n/src/skills/n8n-workflow-operator/references/validation-and-handoff.md +40 -0
- package/dist/plugins/n8n/src/skills/n8n-workflow-operator/references/workflow-authoring.md +45 -0
- package/dist/plugins/n8n/terraform/n8n/README.md +113 -0
- package/dist/plugins/n8n/terraform/n8n/main.tf +918 -0
- package/dist/plugins/n8n/terraform/n8n/outputs.tf +124 -0
- package/dist/plugins/n8n/terraform/n8n/scripts/sync-database.py +250 -0
- package/dist/plugins/n8n/terraform/n8n/scripts/test_sync_database.py +55 -0
- package/dist/plugins/n8n/terraform/n8n/variables.tf +354 -0
- package/dist/plugins/n8n/test/image-build.test.ts +118 -0
- package/dist/plugins/n8n/test/integrated-app-contract.test.ts +353 -0
- package/dist/plugins/n8n/test/manifest.test.ts +278 -0
- package/dist/plugins/n8n/test/package-config.test.ts +57 -0
- package/dist/plugins/n8n/tsconfig.json +8 -0
- package/dist/plugins/sendgrid/README.md +30 -0
- package/dist/plugins/sendgrid/package.json +22 -0
- package/dist/plugins/sendgrid/src/index.ts +36 -0
- package/dist/plugins/sendgrid/src/manifest.ts +37 -0
- package/dist/plugins/sendgrid/src/provider-contract.ts +14 -0
- package/dist/plugins/sendgrid/test/manifest.test.ts +80 -0
- package/dist/plugins/sendgrid/tsconfig.json +8 -0
- package/dist/plugins/twenty/README.md +78 -0
- package/dist/plugins/twenty/package.json +27 -0
- package/dist/plugins/twenty/scripts/__tests__/sync-thinkwork-app.test.mjs +67 -0
- package/dist/plugins/twenty/scripts/sync-thinkwork-app.mjs +185 -0
- package/dist/plugins/twenty/scripts/wire-thinkwork-workflow.mjs +658 -0
- package/dist/plugins/twenty/smoke/twenty-managed-app-smoke.mjs +359 -0
- package/dist/plugins/twenty/smoke/twenty-mcp-oauth-smoke.mjs +574 -0
- package/dist/plugins/twenty/src/api/cutover.ts +209 -0
- package/dist/plugins/twenty/src/deployment/managed-app.ts +253 -0
- package/dist/plugins/twenty/src/index.ts +55 -0
- package/dist/plugins/twenty/src/manifest.ts +259 -0
- package/dist/plugins/twenty/terraform/twenty/README.md +97 -0
- package/dist/plugins/twenty/terraform/twenty/main.tf +829 -0
- package/dist/plugins/twenty/terraform/twenty/outputs.tf +79 -0
- package/dist/plugins/twenty/terraform/twenty/variables.tf +268 -0
- package/dist/plugins/twenty/test/api/cutover.test.ts +183 -0
- package/dist/plugins/twenty/test/manifest.test.ts +463 -0
- package/dist/plugins/twenty/tsconfig.json +9 -0
- package/dist/plugins/twenty/twenty-app/.nvmrc +1 -0
- package/dist/plugins/twenty/twenty-app/README.md +148 -0
- package/dist/plugins/twenty/twenty-app/package.json +30 -0
- package/dist/plugins/twenty/twenty-app/src/application-config.ts +44 -0
- package/dist/plugins/twenty/twenty-app/src/constants/universal-identifiers.ts +18 -0
- package/dist/plugins/twenty/twenty-app/src/default-role.ts +24 -0
- package/dist/plugins/twenty/twenty-app/src/front-components/thinkwork-settings.front-component.tsx +299 -0
- package/dist/plugins/twenty/twenty-app/src/logic-functions/thinkwork-webhook.logic-function.ts +354 -0
- package/dist/plugins/twenty/twenty-app/tsconfig.json +14 -0
- package/dist/plugins/twenty/twenty-app/yarn.lock +4144 -0
- package/dist/plugins/workos-auth/package.json +22 -0
- package/dist/plugins/workos-auth/src/index.ts +30 -0
- package/dist/plugins/workos-auth/src/manifest.ts +40 -0
- package/dist/plugins/workos-auth/src/provider-contract.ts +39 -0
- package/dist/plugins/workos-auth/test/manifest.test.ts +94 -0
- package/dist/plugins/workos-auth/tsconfig.json +8 -0
- package/dist/scripts/bootstrap-workspace.sh +162 -0
- package/dist/scripts/post-deploy.sh +290 -0
- package/dist/terraform/examples/greenfield/main.tf +903 -75
- package/dist/terraform/examples/greenfield/terraform.tfvars.example +67 -8
- package/dist/terraform/modules/app/agentcore-code-interpreter/Dockerfile.sandbox-base +2 -2
- package/dist/terraform/modules/app/agentcore-code-interpreter/README.md +8 -9
- package/dist/terraform/modules/app/agentcore-code-interpreter/sandbox/sitecustomize.py +227 -0
- package/dist/terraform/modules/app/agentcore-code-interpreter/sandbox/test_sitecustomize.py +277 -0
- package/dist/terraform/modules/app/agentcore-memory/README.md +6 -6
- package/dist/terraform/modules/app/agentcore-memory/main.tf +1 -1
- package/dist/terraform/modules/app/agentcore-memory/scripts/create_or_find_memory.sh +40 -9
- package/dist/terraform/modules/app/agentcore-pi/README.md +61 -0
- package/dist/terraform/modules/app/agentcore-pi/main.tf +453 -0
- package/dist/terraform/modules/app/agentcore-pi/outputs.tf +38 -0
- package/dist/terraform/modules/app/agentcore-pi/variables.tf +145 -0
- package/dist/terraform/modules/app/agentcore-platform/main.tf +83 -0
- package/dist/terraform/modules/app/agentcore-runtime/main.tf +9 -11
- package/dist/terraform/modules/app/appsync-subscriptions/main.tf +5 -1
- package/dist/terraform/modules/app/customer-domain/main.tf +483 -0
- package/dist/terraform/modules/app/deployment-control-plane/README.md +47 -0
- package/dist/terraform/modules/app/deployment-control-plane/buildspec.yml +24 -0
- package/dist/terraform/modules/app/deployment-control-plane/main.tf +614 -0
- package/dist/terraform/modules/app/deployment-control-plane/outputs.tf +54 -0
- package/dist/terraform/modules/app/deployment-control-plane/runner.py +4720 -0
- package/dist/terraform/modules/app/deployment-control-plane/test_runner_bundle.py +3026 -0
- package/dist/terraform/modules/app/deployment-control-plane/variables.tf +96 -0
- package/dist/terraform/modules/app/hindsight-memory/README.md +25 -23
- package/dist/terraform/modules/app/hindsight-memory/main.tf +26 -2
- package/dist/terraform/modules/app/lambda-api/eval-fanout.tf +17 -50
- package/dist/terraform/modules/app/lambda-api/handlers.tf +1290 -679
- package/dist/terraform/modules/app/lambda-api/iam-grouped.tf +933 -0
- package/dist/terraform/modules/app/lambda-api/main.tf +14 -527
- package/dist/terraform/modules/app/lambda-api/mcp-oauth.tf +3 -19
- package/dist/terraform/modules/app/lambda-api/outputs.tf +50 -6
- package/dist/terraform/modules/app/lambda-api/runtime-config.tf +163 -0
- package/dist/terraform/modules/app/lambda-api/slack-app-secrets.tf +3 -1
- package/dist/terraform/modules/app/lambda-api/stripe-secrets.tf +3 -1
- package/dist/terraform/modules/app/lambda-api/variables.tf +240 -53
- package/dist/terraform/modules/app/lambda-api/workspace-events.tf +2 -25
- package/dist/terraform/modules/app/ses-email/main.tf +143 -30
- package/dist/terraform/modules/app/static-site/main.tf +4 -3
- package/dist/terraform/modules/app/www-dns/main.tf +45 -5
- package/dist/terraform/modules/app/www-dns/outputs.tf +10 -0
- package/dist/terraform/modules/app/www-dns/variables.tf +24 -0
- package/dist/terraform/modules/data/aurora-postgres/main.tf +15 -1
- package/dist/terraform/modules/data/compliance-exports-bucket/main.tf +1 -1
- package/dist/terraform/modules/data/compliance-exports-bucket/variables.tf +6 -0
- package/dist/terraform/modules/foundation/cognito/main.tf +167 -11
- package/dist/terraform/modules/foundation/cognito/outputs.tf +5 -0
- package/dist/terraform/modules/foundation/cognito/variables.tf +110 -0
- package/dist/terraform/modules/foundation/vpc/main.tf +45 -3
- package/dist/terraform/modules/foundation/vpc/outputs.tf +20 -0
- package/dist/terraform/modules/foundation/vpc/variables.tf +18 -0
- package/dist/terraform/modules/thinkwork/README.md +71 -0
- package/dist/terraform/modules/thinkwork/main.tf +1325 -174
- package/dist/terraform/modules/thinkwork/outputs.tf +457 -24
- package/dist/terraform/modules/thinkwork/variables.tf +1169 -93
- package/dist/terraform/schema.graphql +82 -9
- package/dist/workspace-defaults/files/AGENTS.md +344 -0
- package/dist/workspace-defaults/files/CONTEXT.md +8 -0
- package/dist/workspace-defaults/files/GUARDRAILS.md +51 -0
- package/dist/workspace-defaults/files/MEMORY_GUIDE.md +65 -0
- package/dist/workspace-defaults/files/ROUTER.md +24 -0
- package/dist/workspace-defaults/files/SPACE.md +62 -0
- package/dist/workspace-defaults/files/TOOLS.md +65 -0
- package/dist/workspace-defaults/files/USER.md +40 -0
- package/dist/workspace-defaults/files/mcp.json +3 -0
- package/dist/workspace-defaults/files/memory/contacts.md +13 -0
- package/dist/workspace-defaults/files/memory/lessons.md +13 -0
- package/dist/workspace-defaults/files/memory/preferences.md +12 -0
- package/dist/workspace-defaults/files/skills/.gitkeep +1 -0
- package/dist/workspace-defaults/files/skills/artifact-builder/SKILL.md +79 -0
- package/dist/workspace-defaults/files/skills/artifact-builder/references/crm-dashboard.md +166 -0
- package/dist/workspace-defaults/files/skills/automation-loop-designer/SKILL.md +120 -0
- package/dist/workspace-defaults/files/skills/skill-creator/LICENSE.txt +202 -0
- package/dist/workspace-defaults/files/skills/skill-creator/SKILL.md +485 -0
- package/dist/workspace-defaults/files/skills/skill-creator/UPSTREAM.json +124 -0
- package/dist/workspace-defaults/files/skills/skill-creator/agents/analyzer.md +274 -0
- package/dist/workspace-defaults/files/skills/skill-creator/agents/comparator.md +202 -0
- package/dist/workspace-defaults/files/skills/skill-creator/agents/grader.md +223 -0
- package/dist/workspace-defaults/files/skills/skill-creator/assets/eval_review.html +146 -0
- package/dist/workspace-defaults/files/skills/skill-creator/eval-viewer/generate_review.py +471 -0
- package/dist/workspace-defaults/files/skills/skill-creator/eval-viewer/viewer.html +1325 -0
- package/dist/workspace-defaults/files/skills/skill-creator/references/schemas.md +430 -0
- package/dist/workspace-defaults/files/skills/skill-creator/scripts/__init__.py +0 -0
- package/dist/workspace-defaults/files/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
- package/dist/workspace-defaults/files/skills/skill-creator/scripts/generate_report.py +326 -0
- package/dist/workspace-defaults/files/skills/skill-creator/scripts/improve_description.py +247 -0
- package/dist/workspace-defaults/files/skills/skill-creator/scripts/package_skill.py +136 -0
- package/dist/workspace-defaults/files/skills/skill-creator/scripts/quick_validate.py +103 -0
- package/dist/workspace-defaults/files/skills/skill-creator/scripts/run_eval.py +310 -0
- package/dist/workspace-defaults/files/skills/skill-creator/scripts/run_loop.py +328 -0
- package/dist/workspace-defaults/files/skills/skill-creator/scripts/utils.py +47 -0
- package/package.json +1 -1
- package/dist/terraform/modules/app/agentcore-flue/README.md +0 -58
- package/dist/terraform/modules/app/agentcore-flue/main.tf +0 -323
- package/dist/terraform/modules/app/agentcore-flue/outputs.tf +0 -23
- package/dist/terraform/modules/app/agentcore-flue/variables.tf +0 -97
- package/dist/terraform/modules/app/computer-runtime/README.md +0 -15
- package/dist/terraform/modules/app/computer-runtime/main.tf +0 -406
- package/dist/terraform/modules/app/computer-runtime/outputs.tf +0 -75
- package/dist/terraform/modules/app/computer-runtime/variables.tf +0 -66
|
@@ -0,0 +1,661 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* LastMile plugin end-to-end smoke (plan 2026-06-12-001 U9).
|
|
4
|
+
*
|
|
5
|
+
* Two phases with a MANUAL OAuth step in between (the WorkOS AuthKit
|
|
6
|
+
* consent cannot be completed headlessly):
|
|
7
|
+
*
|
|
8
|
+
* Phase 1 (default) — discovery drift guard against the live
|
|
9
|
+
* RFC 9728 metadata, installPlugin as tenant admin (or verify an
|
|
10
|
+
* existing install), assert state 'installed' with 4 provisioned
|
|
11
|
+
* components, then start activation for the test user and PRINT the
|
|
12
|
+
* authorizeUrl. A human opens that URL in a browser signed in as the
|
|
13
|
+
* activated test user and completes consent.
|
|
14
|
+
*
|
|
15
|
+
* Phase 2 (--post-activation) — asserts myPluginActivations is
|
|
16
|
+
* 'active' for the activated user and NOT active for a second,
|
|
17
|
+
* non-activated user; optionally proves the live tool surface through
|
|
18
|
+
* the MCP proxy (per-user Cognito token): tools/list must expose the
|
|
19
|
+
* three lastmile-- servers for the activated user and exclude them
|
|
20
|
+
* for the non-activated user, plus an optional read tool call
|
|
21
|
+
* (opportunities_list) per SMOKE_LASTMILE_MCP_CALL=1.
|
|
22
|
+
*
|
|
23
|
+
* THNK-37 catalog freshness note: after a LastMile version bump, use
|
|
24
|
+
* Settings -> Plugins or GraphQL `refreshPluginCatalog` to prove the deployed
|
|
25
|
+
* API has refreshed the signed GitHub-backed catalog before running this
|
|
26
|
+
* install/upgrade smoke. This smoke verifies the ThinkWork install,
|
|
27
|
+
* activation, and MCP proxy path for the verified catalog version.
|
|
28
|
+
*
|
|
29
|
+
* Dry-run is the default. Set SMOKE_ENABLE_LASTMILE_PLUGIN=1 to run live.
|
|
30
|
+
*
|
|
31
|
+
* Live env (GraphQL, service-secret path):
|
|
32
|
+
* VITE_GRAPHQL_HTTP_URL or GRAPHQL_HTTP_URL or API_GRAPHQL_URL
|
|
33
|
+
* API_AUTH_SECRET or THINKWORK_API_SECRET
|
|
34
|
+
* SMOKE_TENANT_ID=<tenant uuid>
|
|
35
|
+
* SMOKE_ADMIN_USER_ID=<users.id with owner/admin tenant role>
|
|
36
|
+
* SMOKE_ACTIVATED_USER_ID=<users.id of the user who will/did activate>
|
|
37
|
+
* SMOKE_NON_ACTIVATED_USER_ID=<users.id of a user who must NOT activate>
|
|
38
|
+
*
|
|
39
|
+
* Optional phase-2 tool-surface proof (Cognito path through /api/mcp):
|
|
40
|
+
* SMOKE_API_BASE_URL or VITE_API_URL
|
|
41
|
+
* SMOKE_COGNITO_ID_TOKEN=<activated user's Cognito id token>
|
|
42
|
+
* SMOKE_AGENT_ID=<agent id in the tenant>
|
|
43
|
+
* SMOKE_NONACTIVATED_COGNITO_ID_TOKEN=<non-activated user's id token>
|
|
44
|
+
* SMOKE_LASTMILE_MCP_CALL=1 # run opportunities_list on CRM
|
|
45
|
+
* SMOKE_LASTMILE_OPPORTUNITIES_ARGS='{...}' # optional tool arguments
|
|
46
|
+
* SMOKE_LASTMILE_TASKS_TOOL / SMOKE_LASTMILE_ROUTING_TOOL
|
|
47
|
+
* — opt-in tool names for the tasks/routing call proof. No default:
|
|
48
|
+
* opportunities_list is the only LastMile tool name verified as
|
|
49
|
+
* read-safe, so the other two servers are proven by their
|
|
50
|
+
* authenticated tools/list round-trip unless a tool is named.
|
|
51
|
+
*
|
|
52
|
+
* NOTE — per-stage endpoint gap: the manifest pins LastMile's
|
|
53
|
+
* DEVELOP-stage endpoints (dev-mcp.lastmile-tei.com); prod is
|
|
54
|
+
* mcp.lastmile-tei.com. Per-stage endpoint parameterization is a known
|
|
55
|
+
* v1 gap (see the manifest header).
|
|
56
|
+
*/
|
|
57
|
+
|
|
58
|
+
import { randomUUID } from "node:crypto";
|
|
59
|
+
import fs from "node:fs";
|
|
60
|
+
import path from "node:path";
|
|
61
|
+
import process from "node:process";
|
|
62
|
+
|
|
63
|
+
const LIVE_ENABLED = process.env.SMOKE_ENABLE_LASTMILE_PLUGIN === "1";
|
|
64
|
+
const POST_ACTIVATION = process.argv.includes("--post-activation");
|
|
65
|
+
const CALL_ENABLED = process.env.SMOKE_LASTMILE_MCP_CALL === "1";
|
|
66
|
+
const TIMEOUT_MS = Number(process.env.SMOKE_TIMEOUT_MS || 20_000);
|
|
67
|
+
|
|
68
|
+
// Expected LastMile contract — MUST stay in sync with
|
|
69
|
+
// plugins/lastmile/src/manifest.ts and
|
|
70
|
+
// .../lastmile/discovery.fixture.ts (the unit test pins manifest↔fixture;
|
|
71
|
+
// this smoke pins fixture↔live).
|
|
72
|
+
const PLUGIN_KEY = "lastmile";
|
|
73
|
+
const MCP_BASE = "https://dev-mcp.lastmile-tei.com";
|
|
74
|
+
const AUTH_DOMAIN = "https://straightforward-dragon-14-staging.authkit.app";
|
|
75
|
+
const EXPECTED_SCOPES = ["openid", "email", "profile", "offline_access"];
|
|
76
|
+
const SERVER_KEYS = ["crm", "tasks", "routing"];
|
|
77
|
+
const EXPECTED_SKILL_FOLDER = "skills/lastmile--crm-basics/";
|
|
78
|
+
const serverSlug = (key) => `${PLUGIN_KEY}--${key}`;
|
|
79
|
+
|
|
80
|
+
const env = { ...loadEnvFile(), ...process.env };
|
|
81
|
+
|
|
82
|
+
const graphqlUrl = first(
|
|
83
|
+
env.VITE_GRAPHQL_HTTP_URL,
|
|
84
|
+
env.GRAPHQL_HTTP_URL,
|
|
85
|
+
env.API_GRAPHQL_URL,
|
|
86
|
+
);
|
|
87
|
+
const apiSecret = first(env.API_AUTH_SECRET, env.THINKWORK_API_SECRET);
|
|
88
|
+
const tenantId = first(env.SMOKE_TENANT_ID, env.TENANT_ID);
|
|
89
|
+
const adminUserId = first(env.SMOKE_ADMIN_USER_ID);
|
|
90
|
+
const activatedUserId = first(env.SMOKE_ACTIVATED_USER_ID);
|
|
91
|
+
const nonActivatedUserId = first(env.SMOKE_NON_ACTIVATED_USER_ID);
|
|
92
|
+
const apiBaseUrl = first(env.SMOKE_API_BASE_URL, env.VITE_API_URL);
|
|
93
|
+
const cognitoIdToken = first(env.SMOKE_COGNITO_ID_TOKEN);
|
|
94
|
+
const nonActivatedIdToken = first(env.SMOKE_NONACTIVATED_COGNITO_ID_TOKEN);
|
|
95
|
+
const agentId = first(env.SMOKE_AGENT_ID, env.AGENT_ID);
|
|
96
|
+
const opportunityArgs = parseJsonEnv(
|
|
97
|
+
"SMOKE_LASTMILE_OPPORTUNITIES_ARGS",
|
|
98
|
+
env.SMOKE_LASTMILE_OPPORTUNITIES_ARGS,
|
|
99
|
+
{},
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
const checks = [];
|
|
103
|
+
|
|
104
|
+
function pass(name, detail) {
|
|
105
|
+
checks.push({ name, ok: true, ...(detail ? { detail } : {}) });
|
|
106
|
+
console.log(`PASS — ${name}${detail ? `: ${stringify(detail)}` : ""}`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function failCheck(name, detail) {
|
|
110
|
+
checks.push({ name, ok: false, ...(detail ? { detail } : {}) });
|
|
111
|
+
console.log(`FAIL — ${name}${detail ? `: ${stringify(detail)}` : ""}`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function skip(name, reason) {
|
|
115
|
+
checks.push({ name, ok: true, skipped: true, reason });
|
|
116
|
+
console.log(`SKIP — ${name}: ${reason}`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function stringify(value) {
|
|
120
|
+
return typeof value === "string" ? value : JSON.stringify(value);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (!LIVE_ENABLED) {
|
|
124
|
+
console.log(
|
|
125
|
+
JSON.stringify(
|
|
126
|
+
{
|
|
127
|
+
ok: true,
|
|
128
|
+
skippedLive: true,
|
|
129
|
+
reason:
|
|
130
|
+
"set SMOKE_ENABLE_LASTMILE_PLUGIN=1 to run the LastMile plugin smoke",
|
|
131
|
+
phases: {
|
|
132
|
+
phase1:
|
|
133
|
+
"node plugins/lastmile/smoke/lastmile-plugin-smoke.mjs — discovery drift guard + installPlugin + activatePlugin authorizeUrl",
|
|
134
|
+
manualStep:
|
|
135
|
+
"open the printed authorizeUrl in a browser signed in as SMOKE_ACTIVATED_USER_ID and complete the WorkOS AuthKit consent",
|
|
136
|
+
phase2:
|
|
137
|
+
"node plugins/lastmile/smoke/lastmile-plugin-smoke.mjs --post-activation — activation + per-user tool surface assertions",
|
|
138
|
+
},
|
|
139
|
+
requiredWhenRunning: [
|
|
140
|
+
"VITE_GRAPHQL_HTTP_URL or GRAPHQL_HTTP_URL",
|
|
141
|
+
"API_AUTH_SECRET or THINKWORK_API_SECRET",
|
|
142
|
+
"SMOKE_TENANT_ID",
|
|
143
|
+
"SMOKE_ADMIN_USER_ID (owner/admin tenant role)",
|
|
144
|
+
"SMOKE_ACTIVATED_USER_ID",
|
|
145
|
+
"SMOKE_NON_ACTIVATED_USER_ID (phase 2)",
|
|
146
|
+
],
|
|
147
|
+
optionalToolSurfaceProof: [
|
|
148
|
+
"SMOKE_API_BASE_URL or VITE_API_URL",
|
|
149
|
+
"SMOKE_COGNITO_ID_TOKEN (activated user)",
|
|
150
|
+
"SMOKE_AGENT_ID",
|
|
151
|
+
"SMOKE_NONACTIVATED_COGNITO_ID_TOKEN (exclusion proof)",
|
|
152
|
+
"SMOKE_LASTMILE_MCP_CALL=1 (CRM opportunities_list call)",
|
|
153
|
+
],
|
|
154
|
+
},
|
|
155
|
+
null,
|
|
156
|
+
2,
|
|
157
|
+
),
|
|
158
|
+
);
|
|
159
|
+
process.exit(0);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
try {
|
|
163
|
+
if (POST_ACTIVATION) {
|
|
164
|
+
await runPhase2();
|
|
165
|
+
} else {
|
|
166
|
+
await runPhase1();
|
|
167
|
+
}
|
|
168
|
+
const failed = checks.filter((check) => !check.ok);
|
|
169
|
+
console.log(
|
|
170
|
+
JSON.stringify(
|
|
171
|
+
{ ok: failed.length === 0, phase: POST_ACTIVATION ? 2 : 1, checks },
|
|
172
|
+
null,
|
|
173
|
+
2,
|
|
174
|
+
),
|
|
175
|
+
);
|
|
176
|
+
process.exit(failed.length === 0 ? 0 : 1);
|
|
177
|
+
} catch (error) {
|
|
178
|
+
console.error(
|
|
179
|
+
JSON.stringify(
|
|
180
|
+
{
|
|
181
|
+
ok: false,
|
|
182
|
+
phase: POST_ACTIVATION ? 2 : 1,
|
|
183
|
+
error: error instanceof Error ? error.message : String(error),
|
|
184
|
+
checks,
|
|
185
|
+
},
|
|
186
|
+
null,
|
|
187
|
+
2,
|
|
188
|
+
),
|
|
189
|
+
);
|
|
190
|
+
process.exit(1);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// ---------------------------------------------------------------------------
|
|
194
|
+
// Phase 1 — discovery drift guard + install + activation kickoff
|
|
195
|
+
// ---------------------------------------------------------------------------
|
|
196
|
+
|
|
197
|
+
async function runPhase1() {
|
|
198
|
+
requireEnv("VITE_GRAPHQL_HTTP_URL or GRAPHQL_HTTP_URL", graphqlUrl);
|
|
199
|
+
requireEnv("API_AUTH_SECRET or THINKWORK_API_SECRET", apiSecret);
|
|
200
|
+
requireEnv("SMOKE_TENANT_ID", tenantId);
|
|
201
|
+
requireEnv("SMOKE_ADMIN_USER_ID", adminUserId);
|
|
202
|
+
requireEnv("SMOKE_ACTIVATED_USER_ID", activatedUserId);
|
|
203
|
+
|
|
204
|
+
await assertDiscoveryMetadata();
|
|
205
|
+
|
|
206
|
+
const install = await ensureInstalled();
|
|
207
|
+
assertInstallShape(install);
|
|
208
|
+
|
|
209
|
+
const { activatePlugin } = await gql(
|
|
210
|
+
`mutation LastmileSmokeActivate($input: ActivatePluginInput!) {
|
|
211
|
+
activatePlugin(input: $input) { authorizeUrl }
|
|
212
|
+
}`,
|
|
213
|
+
{ input: { installId: install.id } },
|
|
214
|
+
activatedUserId,
|
|
215
|
+
);
|
|
216
|
+
pass("activatePlugin returned an authorizeUrl", {
|
|
217
|
+
installId: install.id,
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
console.log("\n=== MANUAL STEP REQUIRED ===");
|
|
221
|
+
console.log(
|
|
222
|
+
`Open this URL in a browser signed in as user ${activatedUserId} and complete the OAuth consent:`,
|
|
223
|
+
);
|
|
224
|
+
console.log(`\n${activatePlugin.authorizeUrl}\n`);
|
|
225
|
+
console.log(
|
|
226
|
+
"Then run: SMOKE_ENABLE_LASTMILE_PLUGIN=1 node plugins/lastmile/smoke/lastmile-plugin-smoke.mjs --post-activation",
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async function assertDiscoveryMetadata() {
|
|
231
|
+
for (const key of SERVER_KEYS) {
|
|
232
|
+
const url = `${MCP_BASE}/.well-known/oauth-protected-resource/${key}`;
|
|
233
|
+
const metadata = await fetchJson(url);
|
|
234
|
+
const problems = [];
|
|
235
|
+
if (metadata.resource !== `${MCP_BASE}/${key}`) {
|
|
236
|
+
problems.push(
|
|
237
|
+
`resource ${metadata.resource} != manifest endpoint ${MCP_BASE}/${key}`,
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
if (!(metadata.authorization_servers ?? []).includes(AUTH_DOMAIN)) {
|
|
241
|
+
problems.push(
|
|
242
|
+
`authorization_servers ${JSON.stringify(metadata.authorization_servers)} missing manifest auth domain ${AUTH_DOMAIN}`,
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
const liveScopes = [...(metadata.scopes_supported ?? [])].sort();
|
|
246
|
+
if (
|
|
247
|
+
JSON.stringify(liveScopes) !== JSON.stringify([...EXPECTED_SCOPES].sort())
|
|
248
|
+
) {
|
|
249
|
+
problems.push(
|
|
250
|
+
`scopes_supported ${JSON.stringify(liveScopes)} != manifest scopes ${JSON.stringify(EXPECTED_SCOPES)}`,
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
if (problems.length > 0) {
|
|
254
|
+
failCheck(`discovery metadata for ${key} matches the manifest`, problems);
|
|
255
|
+
} else {
|
|
256
|
+
pass(`discovery metadata for ${key} matches the manifest`);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
async function ensureInstalled() {
|
|
262
|
+
const { pluginInstalls } = await gql(
|
|
263
|
+
`query LastmileSmokeInstalls {
|
|
264
|
+
pluginInstalls {
|
|
265
|
+
id pluginKey pinnedVersion state lastError
|
|
266
|
+
components { componentKey componentType state handlerRef lastError }
|
|
267
|
+
}
|
|
268
|
+
}`,
|
|
269
|
+
{},
|
|
270
|
+
adminUserId,
|
|
271
|
+
);
|
|
272
|
+
const existing = pluginInstalls.find(
|
|
273
|
+
(install) => install.pluginKey === PLUGIN_KEY,
|
|
274
|
+
);
|
|
275
|
+
if (existing) {
|
|
276
|
+
pass("lastmile install already exists (skipping installPlugin)", {
|
|
277
|
+
installId: existing.id,
|
|
278
|
+
state: existing.state,
|
|
279
|
+
pinnedVersion: existing.pinnedVersion,
|
|
280
|
+
});
|
|
281
|
+
return existing;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const { installPlugin } = await gql(
|
|
285
|
+
`mutation LastmileSmokeInstall($input: InstallPluginInput!) {
|
|
286
|
+
installPlugin(input: $input) {
|
|
287
|
+
id pluginKey pinnedVersion state lastError
|
|
288
|
+
components { componentKey componentType state handlerRef lastError }
|
|
289
|
+
}
|
|
290
|
+
}`,
|
|
291
|
+
{
|
|
292
|
+
input: {
|
|
293
|
+
pluginKey: PLUGIN_KEY,
|
|
294
|
+
idempotencyKey: `lastmile-smoke-${randomUUID()}`,
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
adminUserId,
|
|
298
|
+
);
|
|
299
|
+
pass("installPlugin completed", {
|
|
300
|
+
installId: installPlugin.id,
|
|
301
|
+
state: installPlugin.state,
|
|
302
|
+
pinnedVersion: installPlugin.pinnedVersion,
|
|
303
|
+
});
|
|
304
|
+
return installPlugin;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function assertInstallShape(install) {
|
|
308
|
+
if (install.state === "installed") {
|
|
309
|
+
pass("install state is 'installed'");
|
|
310
|
+
} else {
|
|
311
|
+
failCheck("install state is 'installed'", {
|
|
312
|
+
state: install.state,
|
|
313
|
+
lastError: install.lastError,
|
|
314
|
+
componentErrors: install.components
|
|
315
|
+
.filter((component) => component.lastError)
|
|
316
|
+
.map((component) => ({
|
|
317
|
+
componentKey: component.componentKey,
|
|
318
|
+
state: component.state,
|
|
319
|
+
lastError: component.lastError,
|
|
320
|
+
})),
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (install.components.length === 4) {
|
|
325
|
+
pass("install has 4 components");
|
|
326
|
+
} else {
|
|
327
|
+
failCheck("install has 4 components", {
|
|
328
|
+
got: install.components.map((component) => component.componentKey),
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
for (const key of SERVER_KEYS) {
|
|
333
|
+
const component = install.components.find(
|
|
334
|
+
(candidate) =>
|
|
335
|
+
candidate.componentKey === key &&
|
|
336
|
+
candidate.componentType === "mcp-server",
|
|
337
|
+
);
|
|
338
|
+
if (component?.state === "provisioned") {
|
|
339
|
+
pass(`mcp-server component '${key}' is provisioned`, {
|
|
340
|
+
handlerRef: parseHandlerRef(component.handlerRef),
|
|
341
|
+
});
|
|
342
|
+
} else {
|
|
343
|
+
failCheck(`mcp-server component '${key}' is provisioned`, {
|
|
344
|
+
state: component?.state ?? "missing",
|
|
345
|
+
lastError: component?.lastError ?? null,
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const skills = install.components.find(
|
|
351
|
+
(candidate) => candidate.componentType === "skills",
|
|
352
|
+
);
|
|
353
|
+
const skillsRef = skills ? parseHandlerRef(skills.handlerRef) : null;
|
|
354
|
+
if (
|
|
355
|
+
skills?.state === "provisioned" &&
|
|
356
|
+
(skillsRef?.workspaceFolders ?? []).includes(EXPECTED_SKILL_FOLDER)
|
|
357
|
+
) {
|
|
358
|
+
pass("skills component is provisioned with the crm-basics folder", {
|
|
359
|
+
handlerRef: skillsRef,
|
|
360
|
+
});
|
|
361
|
+
} else {
|
|
362
|
+
failCheck("skills component is provisioned with the crm-basics folder", {
|
|
363
|
+
state: skills?.state ?? "missing",
|
|
364
|
+
handlerRef: skillsRef,
|
|
365
|
+
lastError: skills?.lastError ?? null,
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// ---------------------------------------------------------------------------
|
|
371
|
+
// Phase 2 — activation state + per-user tool surface
|
|
372
|
+
// ---------------------------------------------------------------------------
|
|
373
|
+
|
|
374
|
+
async function runPhase2() {
|
|
375
|
+
requireEnv("VITE_GRAPHQL_HTTP_URL or GRAPHQL_HTTP_URL", graphqlUrl);
|
|
376
|
+
requireEnv("API_AUTH_SECRET or THINKWORK_API_SECRET", apiSecret);
|
|
377
|
+
requireEnv("SMOKE_TENANT_ID", tenantId);
|
|
378
|
+
requireEnv("SMOKE_ACTIVATED_USER_ID", activatedUserId);
|
|
379
|
+
requireEnv("SMOKE_NON_ACTIVATED_USER_ID", nonActivatedUserId);
|
|
380
|
+
|
|
381
|
+
const activatedRows = await myActivations(activatedUserId);
|
|
382
|
+
const active = activatedRows.find(
|
|
383
|
+
(row) => row.pluginKey === PLUGIN_KEY && row.status === "active",
|
|
384
|
+
);
|
|
385
|
+
if (active) {
|
|
386
|
+
pass("activated user has an 'active' lastmile activation", {
|
|
387
|
+
activationId: active.id,
|
|
388
|
+
grantedScopes: active.grantedScopes,
|
|
389
|
+
});
|
|
390
|
+
} else {
|
|
391
|
+
failCheck("activated user has an 'active' lastmile activation", {
|
|
392
|
+
rows: activatedRows.map((row) => ({
|
|
393
|
+
pluginKey: row.pluginKey,
|
|
394
|
+
status: row.status,
|
|
395
|
+
})),
|
|
396
|
+
hint: "did the manual OAuth step complete? Re-run phase 1 to mint a fresh authorizeUrl.",
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const nonActivatedRows = await myActivations(nonActivatedUserId);
|
|
401
|
+
const leaked = nonActivatedRows.find(
|
|
402
|
+
(row) => row.pluginKey === PLUGIN_KEY && row.status === "active",
|
|
403
|
+
);
|
|
404
|
+
if (leaked) {
|
|
405
|
+
failCheck("non-activated user has NO active lastmile activation", {
|
|
406
|
+
activationId: leaked.id,
|
|
407
|
+
});
|
|
408
|
+
} else {
|
|
409
|
+
pass("non-activated user has NO active lastmile activation");
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
await assertToolSurface();
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
async function myActivations(userId) {
|
|
416
|
+
const { myPluginActivations } = await gql(
|
|
417
|
+
`query LastmileSmokeActivations {
|
|
418
|
+
myPluginActivations { id pluginKey status grantedScopes pluginInstallId }
|
|
419
|
+
}`,
|
|
420
|
+
{},
|
|
421
|
+
userId,
|
|
422
|
+
);
|
|
423
|
+
return myPluginActivations;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
async function assertToolSurface() {
|
|
427
|
+
if (!apiBaseUrl || !cognitoIdToken || !agentId) {
|
|
428
|
+
skip(
|
|
429
|
+
"activated user's MCP tool surface includes the three lastmile servers",
|
|
430
|
+
"set SMOKE_API_BASE_URL, SMOKE_COGNITO_ID_TOKEN (activated user), and SMOKE_AGENT_ID to prove the live tool surface through /api/mcp/tools/list",
|
|
431
|
+
);
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// tools/list performs an authenticated MCP round-trip per server with
|
|
436
|
+
// the activation's bearer token — it is itself the per-server proof.
|
|
437
|
+
const toolsByServer = await listToolsByServer(cognitoIdToken);
|
|
438
|
+
for (const key of SERVER_KEYS) {
|
|
439
|
+
const slug = serverSlug(key);
|
|
440
|
+
const tools = toolsByServer.get(slug) ?? [];
|
|
441
|
+
if (tools.length > 0) {
|
|
442
|
+
pass(`activated user's tools/list exposes ${slug}`, {
|
|
443
|
+
toolCount: tools.length,
|
|
444
|
+
sample: tools.slice(0, 5).map((tool) => tool.tool),
|
|
445
|
+
});
|
|
446
|
+
} else {
|
|
447
|
+
failCheck(`activated user's tools/list exposes ${slug}`);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
await runToolCalls(toolsByServer);
|
|
452
|
+
|
|
453
|
+
if (!nonActivatedIdToken) {
|
|
454
|
+
skip(
|
|
455
|
+
"non-activated user's tool surface excludes lastmile servers",
|
|
456
|
+
"set SMOKE_NONACTIVATED_COGNITO_ID_TOKEN to prove the fail-closed exclusion through tools/list (the activation-row assertion above covers the gate input)",
|
|
457
|
+
);
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
const excludedSurface = await listToolsByServer(nonActivatedIdToken);
|
|
461
|
+
const leakedServers = SERVER_KEYS.map(serverSlug).filter(
|
|
462
|
+
(slug) => (excludedSurface.get(slug) ?? []).length > 0,
|
|
463
|
+
);
|
|
464
|
+
if (leakedServers.length === 0) {
|
|
465
|
+
pass("non-activated user's tools/list excludes all lastmile servers");
|
|
466
|
+
} else {
|
|
467
|
+
failCheck("non-activated user's tools/list excludes all lastmile servers", {
|
|
468
|
+
leakedServers,
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
async function runToolCalls(toolsByServer) {
|
|
474
|
+
if (!CALL_ENABLED) {
|
|
475
|
+
skip(
|
|
476
|
+
"per-server tool call",
|
|
477
|
+
"set SMOKE_LASTMILE_MCP_CALL=1 to call opportunities_list on the CRM server",
|
|
478
|
+
);
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// CRM: opportunities_list is the verified read tool.
|
|
483
|
+
await callTool(serverSlug("crm"), "opportunities_list", opportunityArgs);
|
|
484
|
+
|
|
485
|
+
// Tasks/Routing: no verified read-safe tool names — opt in by env.
|
|
486
|
+
for (const [key, toolEnv, argsEnv] of [
|
|
487
|
+
["tasks", "SMOKE_LASTMILE_TASKS_TOOL", "SMOKE_LASTMILE_TASKS_ARGS"],
|
|
488
|
+
["routing", "SMOKE_LASTMILE_ROUTING_TOOL", "SMOKE_LASTMILE_ROUTING_ARGS"],
|
|
489
|
+
]) {
|
|
490
|
+
const toolName = first(env[toolEnv]);
|
|
491
|
+
if (!toolName) {
|
|
492
|
+
const available = (toolsByServer.get(serverSlug(key)) ?? []).map(
|
|
493
|
+
(tool) => tool.tool,
|
|
494
|
+
);
|
|
495
|
+
skip(
|
|
496
|
+
`${key} tool call`,
|
|
497
|
+
`no verified read-safe ${key} tool name; set ${toolEnv} to one of ${JSON.stringify(available.slice(0, 10))} (authenticated tools/list already proved the server)`,
|
|
498
|
+
);
|
|
499
|
+
continue;
|
|
500
|
+
}
|
|
501
|
+
await callTool(
|
|
502
|
+
serverSlug(key),
|
|
503
|
+
toolName,
|
|
504
|
+
parseJsonEnv(argsEnv, env[argsEnv], {}),
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
async function callTool(server, tool, args) {
|
|
510
|
+
const response = await api("/api/mcp/tools/call", cognitoIdToken, {
|
|
511
|
+
agentId,
|
|
512
|
+
server,
|
|
513
|
+
tool,
|
|
514
|
+
arguments: args,
|
|
515
|
+
});
|
|
516
|
+
if (response.isError) {
|
|
517
|
+
failCheck(`tools/call ${server} ${tool}`, {
|
|
518
|
+
content: JSON.stringify(response.content ?? []).slice(0, 800),
|
|
519
|
+
});
|
|
520
|
+
} else {
|
|
521
|
+
pass(`tools/call ${server} ${tool}`, {
|
|
522
|
+
contentPreview: JSON.stringify(response.content ?? []).slice(0, 400),
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
async function listToolsByServer(idToken) {
|
|
528
|
+
const response = await api("/api/mcp/tools/list", idToken, { agentId });
|
|
529
|
+
const byServer = new Map();
|
|
530
|
+
for (const tool of response.tools ?? []) {
|
|
531
|
+
const list = byServer.get(tool.server) ?? [];
|
|
532
|
+
list.push(tool);
|
|
533
|
+
byServer.set(tool.server, list);
|
|
534
|
+
}
|
|
535
|
+
return byServer;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// ---------------------------------------------------------------------------
|
|
539
|
+
// Transport helpers (smoke conventions)
|
|
540
|
+
// ---------------------------------------------------------------------------
|
|
541
|
+
|
|
542
|
+
async function gql(query, variables, principalId) {
|
|
543
|
+
const response = await fetchWithTimeout(graphqlUrl, {
|
|
544
|
+
method: "POST",
|
|
545
|
+
headers: {
|
|
546
|
+
"content-type": "application/json",
|
|
547
|
+
authorization: `Bearer ${apiSecret}`,
|
|
548
|
+
"x-tenant-id": tenantId,
|
|
549
|
+
...(principalId ? { "x-principal-id": principalId } : {}),
|
|
550
|
+
},
|
|
551
|
+
body: JSON.stringify({ query, variables }),
|
|
552
|
+
});
|
|
553
|
+
const text = await response.text();
|
|
554
|
+
if (!response.ok) {
|
|
555
|
+
throw new Error(`GraphQL HTTP ${response.status}: ${text.slice(0, 500)}`);
|
|
556
|
+
}
|
|
557
|
+
const body = JSON.parse(text);
|
|
558
|
+
if (body.errors?.length) {
|
|
559
|
+
throw new Error(`GraphQL errors: ${JSON.stringify(body.errors)}`);
|
|
560
|
+
}
|
|
561
|
+
return body.data;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
async function api(pathname, idToken, body) {
|
|
565
|
+
const url = new URL(pathname, apiBaseUrl).toString();
|
|
566
|
+
const response = await fetchWithTimeout(url, {
|
|
567
|
+
method: "POST",
|
|
568
|
+
headers: {
|
|
569
|
+
authorization: `Bearer ${idToken}`,
|
|
570
|
+
"content-type": "application/json",
|
|
571
|
+
},
|
|
572
|
+
body: JSON.stringify(body),
|
|
573
|
+
});
|
|
574
|
+
const text = await response.text();
|
|
575
|
+
if (!response.ok) {
|
|
576
|
+
throw new Error(`HTTP ${response.status} ${url}: ${text.slice(0, 500)}`);
|
|
577
|
+
}
|
|
578
|
+
return text ? JSON.parse(text) : {};
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
async function fetchJson(url) {
|
|
582
|
+
const response = await fetchWithTimeout(url, {
|
|
583
|
+
headers: { accept: "application/json" },
|
|
584
|
+
});
|
|
585
|
+
const text = await response.text();
|
|
586
|
+
if (!response.ok) {
|
|
587
|
+
throw new Error(`HTTP ${response.status} ${url}: ${text.slice(0, 500)}`);
|
|
588
|
+
}
|
|
589
|
+
return text ? JSON.parse(text) : {};
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
async function fetchWithTimeout(url, options = {}) {
|
|
593
|
+
const controller = new AbortController();
|
|
594
|
+
const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
|
|
595
|
+
try {
|
|
596
|
+
return await fetch(url, { ...options, signal: controller.signal });
|
|
597
|
+
} finally {
|
|
598
|
+
clearTimeout(timer);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
function parseHandlerRef(raw) {
|
|
603
|
+
if (raw == null) return null;
|
|
604
|
+
if (typeof raw === "object") return raw;
|
|
605
|
+
try {
|
|
606
|
+
return JSON.parse(raw);
|
|
607
|
+
} catch {
|
|
608
|
+
return { unparsed: String(raw).slice(0, 200) };
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
function parseJsonEnv(name, value, fallback) {
|
|
613
|
+
if (!value) return fallback;
|
|
614
|
+
try {
|
|
615
|
+
return JSON.parse(value);
|
|
616
|
+
} catch (error) {
|
|
617
|
+
throw new Error(
|
|
618
|
+
`${name} must be valid JSON: ${error instanceof Error ? error.message : String(error)}`,
|
|
619
|
+
);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
function requireEnv(label, value) {
|
|
624
|
+
if (!value) {
|
|
625
|
+
throw new Error(`Missing required live smoke env: ${label}`);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
function loadEnvFile() {
|
|
630
|
+
const explicit = process.env.COMPUTER_ENV_FILE;
|
|
631
|
+
if (explicit === "none") return {};
|
|
632
|
+
const candidates = [explicit, "apps/web/.env", ".env"].filter(Boolean);
|
|
633
|
+
|
|
634
|
+
for (const candidate of candidates) {
|
|
635
|
+
const resolved = path.resolve(candidate);
|
|
636
|
+
if (!fs.existsSync(resolved)) continue;
|
|
637
|
+
return Object.fromEntries(
|
|
638
|
+
fs
|
|
639
|
+
.readFileSync(resolved, "utf8")
|
|
640
|
+
.split(/\r?\n/)
|
|
641
|
+
.map((line) => line.trim())
|
|
642
|
+
.filter((line) => line && !line.startsWith("#") && line.includes("="))
|
|
643
|
+
.map((line) => {
|
|
644
|
+
const index = line.indexOf("=");
|
|
645
|
+
const key = line.slice(0, index).trim();
|
|
646
|
+
const value = line
|
|
647
|
+
.slice(index + 1)
|
|
648
|
+
.trim()
|
|
649
|
+
.replace(/^['"]|['"]$/g, "");
|
|
650
|
+
return [key, value];
|
|
651
|
+
}),
|
|
652
|
+
);
|
|
653
|
+
}
|
|
654
|
+
return {};
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
function first(...values) {
|
|
658
|
+
return values.find(
|
|
659
|
+
(value) => value !== undefined && value !== null && value !== "",
|
|
660
|
+
);
|
|
661
|
+
}
|