thinkwork-cli 0.13.2 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (231) hide show
  1. package/README.md +3 -14
  2. package/dist/{api-client-JYZXYQJR.js → api-client-4P4JZOBX.js} +1 -1
  3. package/dist/{chunk-Z2F6RJFR.js → chunk-4OMDWYZQ.js} +4 -5
  4. package/dist/{chunk-ILZKG5WV.js → chunk-AD2NIWB5.js} +434 -69
  5. package/dist/chunk-MWQ7RO76.js +137 -0
  6. package/dist/{chunk-DVPBI5HM.js → chunk-OLUQ4U2Z.js} +26 -26
  7. package/dist/{chunk-QOERT44A.js → chunk-YQCYD6NE.js} +57 -44
  8. package/dist/cli.js +4400 -16431
  9. package/dist/commands/enterprise/templates/deploy-repo/scripts/apply-release.mjs +0 -1
  10. package/dist/commands/enterprise/templates/deploy-repo/scripts/smoke.mjs +0 -1
  11. package/dist/commands/enterprise/templates/deploy-repo/terraform/main.tf +0 -14
  12. package/dist/{dist-es-CRV46D5B.js → dist-es-2OTNTL4N.js} +4 -4
  13. package/dist/{dist-es-ENBUC3JP.js → dist-es-2XVAH4BQ.js} +2 -2
  14. package/dist/{dist-es-DCY4IC3Y.js → dist-es-3DPDUJAH.js} +2 -2
  15. package/dist/{dist-es-G7PTG75M.js → dist-es-BNZTBBVC.js} +1 -1
  16. package/dist/{dist-es-GKDBE7E6.js → dist-es-CD2RBDAC.js} +1 -1
  17. package/dist/{dist-es-R7FGL2IB.js → dist-es-I4FW57PS.js} +1 -1
  18. package/dist/{dist-es-USORBXZ5.js → dist-es-KVFWJNDY.js} +4 -4
  19. package/dist/{dist-es-HY3535OI.js → dist-es-PS73DWNL.js} +2 -2
  20. package/dist/{dist-es-LDE437AM.js → dist-es-S3NZQFGT.js} +1 -1
  21. package/dist/{dist-es-G54LBMRN.js → dist-es-SGCA4P32.js} +1 -1
  22. package/dist/{dist-es-Q57SZXZ7.js → dist-es-WBM6J4RA.js} +2 -2
  23. package/dist/{dist-es-P26FWFLT.js → dist-es-ZGVADYIF.js} +8 -8
  24. package/dist/drizzle/0227_analyst_reader_role.sql +17 -26
  25. package/dist/drizzle/0230_analyst_rls.sql +28 -45
  26. package/dist/drizzle/0261_native_auth_control_plane.sql +7 -3
  27. package/dist/drizzle/0270_tool_execution_ledger.sql +115 -0
  28. package/dist/drizzle/0271_ontology_twin_declarations.sql +47 -0
  29. package/dist/drizzle/0272_identity_graph_projection_cursor.sql +23 -0
  30. package/dist/drizzle/0273_twin_materialization_suggestions.sql +29 -0
  31. package/dist/drizzle/0274_tenant_mcp_twin_keys.sql +59 -0
  32. package/dist/drizzle/0275_identity_bulk_rebuild_fence.sql +30 -0
  33. package/dist/drizzle/0276_tool_execution_ledger_allow_cascade_delete.sql +18 -0
  34. package/dist/drizzle/0277_knowledge_base_sources.sql +107 -0
  35. package/dist/drizzle/0278_kb_page_transcription.sql +55 -0
  36. package/dist/drizzle/0279_mcp_servers_off_plugin_provenance.sql +93 -0
  37. package/dist/drizzle/0280_work_item_external_refs_drop_plane_provider.sql +46 -0
  38. package/dist/drizzle/0281_tenant_mcp_twin_keys_expiry_suffix.sql +9 -0
  39. package/dist/drizzle/0282_tenant_mcp_twin_keys_grants.sql +24 -0
  40. package/dist/drizzle/0283_agents_agentcore_runtime_dispatch.sql +13 -0
  41. package/dist/drizzle/0284_user_brain_claims.sql +69 -0
  42. package/dist/drizzle/0285_tenant_mcp_twin_keys_trusted_subsystem.sql +30 -0
  43. package/dist/drizzle/0286_brain_analytics_key.sql +26 -0
  44. package/dist/drizzle/0287_brain_subject.sql +25 -0
  45. package/dist/drizzle/0288_multi_lane_identities.sql +25 -0
  46. package/dist/{loadSso-DJVB7BXE.js → loadSso-ETCXTR4I.js} +2 -2
  47. package/dist/{loadSso-JAMNA3WY.js → loadSso-UBD7KUHE.js} +2 -2
  48. package/dist/plugins/README.md +2 -2
  49. package/dist/plugins/catalog/package.json +0 -3
  50. package/dist/plugins/catalog/src/__tests__/build-catalog.test.ts +6 -3
  51. package/dist/plugins/catalog/src/__tests__/catalog.test.ts +21 -21
  52. package/dist/plugins/catalog/src/__tests__/contracts.test.ts +102 -25
  53. package/dist/plugins/catalog/src/__tests__/plugin-package.test.ts +7 -6
  54. package/dist/plugins/catalog/src/__tests__/plugin-registry.test.ts +0 -15
  55. package/dist/plugins/catalog/src/contracts.ts +15 -0
  56. package/dist/plugins/catalog/src/registry/generated-first-party.ts +0 -18
  57. package/dist/plugins/n8n/test/integrated-app-contract.test.ts +1 -1
  58. package/dist/plugins/twenty/package.json +2 -1
  59. package/dist/plugins/twenty/scripts/LAMBDA_DEPLOY.md +114 -0
  60. package/dist/plugins/twenty/scripts/build-lambda.sh +29 -0
  61. package/dist/plugins/twenty/scripts/configure-product-views.ts +195 -0
  62. package/dist/plugins/twenty/scripts/lambda-handler.ts +49 -0
  63. package/dist/plugins/twenty/scripts/lib/__tests__/load-attachments.test.ts +38 -0
  64. package/dist/plugins/twenty/scripts/lib/__tests__/load-records.test.ts +145 -2
  65. package/dist/plugins/twenty/scripts/lib/__tests__/mappers.test.ts +397 -151
  66. package/dist/plugins/twenty/scripts/lib/__tests__/members-ensure.test.ts +28 -129
  67. package/dist/plugins/twenty/scripts/lib/__tests__/provision-members-db.test.ts +10 -0
  68. package/dist/plugins/twenty/scripts/lib/__tests__/schema-ensure.test.ts +13 -8
  69. package/dist/plugins/twenty/scripts/lib/lastmile-reader.ts +222 -2
  70. package/dist/plugins/twenty/scripts/lib/load-attachments.ts +29 -0
  71. package/dist/plugins/twenty/scripts/lib/load-records.ts +160 -43
  72. package/dist/plugins/twenty/scripts/lib/mappers.ts +368 -152
  73. package/dist/plugins/twenty/scripts/lib/members-ensure.ts +43 -268
  74. package/dist/plugins/twenty/scripts/lib/provision-members-db.ts +57 -0
  75. package/dist/plugins/twenty/scripts/lib/schema-ensure.ts +136 -31
  76. package/dist/plugins/twenty/scripts/migrate-lastmile.ts +254 -152
  77. package/dist/plugins/twenty/scripts/provision-twenty-members.ts +44 -1
  78. package/dist/plugins/twenty/scripts/purge-lastmile-import.ts +178 -0
  79. package/dist/plugins/twenty/scripts/restructure-products.ts +312 -0
  80. package/dist/plugins/twenty/scripts/retag-stage-values.ts +362 -0
  81. package/dist/plugins/twenty/test/manifest.test.ts +2 -4
  82. package/dist/scripts/post-deploy.sh +79 -1
  83. package/dist/{signin-UGDWTGS4.js → signin-5QZNRSOO.js} +2 -2
  84. package/dist/{sso-oidc-Q4VXANXY.js → sso-oidc-XK673P54.js} +2 -2
  85. package/dist/{sts-CWLSE3GH.js → sts-OXI2UALF.js} +6 -134
  86. package/dist/{sts-DNZLNVOV.js → sts-SQW4NBK3.js} +6 -134
  87. package/dist/terraform/examples/greenfield/main.tf +233 -175
  88. package/dist/terraform/examples/greenfield/terraform.tfvars.example +12 -8
  89. package/dist/terraform/modules/app/agentcore-admin/main.tf +47 -0
  90. package/dist/terraform/modules/app/agentcore-code-interpreter/README.md +39 -47
  91. package/dist/terraform/modules/app/agentcore-code-interpreter/main.tf +13 -66
  92. package/dist/terraform/modules/app/agentcore-identity/main.tf +106 -26
  93. package/dist/terraform/modules/app/agentcore-identity/outputs.tf +6 -6
  94. package/dist/terraform/modules/app/agentcore-identity/scripts/bootstrap_twenty_oauth_client.sh +216 -99
  95. package/dist/terraform/modules/app/agentcore-identity/scripts/delete_identity.sh +6 -9
  96. package/dist/terraform/modules/app/agentcore-identity/scripts/delete_workload_identity.sh +19 -0
  97. package/dist/terraform/modules/app/agentcore-identity/scripts/ensure_workload_identity.sh +29 -0
  98. package/dist/terraform/modules/app/agentcore-identity/scripts/read_identity.sh +22 -7
  99. package/dist/terraform/modules/app/agentcore-identity/scripts/reconcile_twenty_identity.sh +17 -46
  100. package/dist/terraform/modules/app/agentcore-identity/scripts/reconcile_twenty_provider.mjs +65 -3
  101. package/dist/terraform/modules/app/agentcore-identity/tests/tainted-marker/after.tf +15 -0
  102. package/dist/terraform/modules/app/agentcore-identity/tests/tainted-marker/before.tf +7 -0
  103. package/dist/terraform/modules/app/agentcore-identity/tests/tainted-marker/identity-after.tf +8 -0
  104. package/dist/terraform/modules/app/agentcore-identity/tests/tainted-marker/identity-before.tf +9 -0
  105. package/dist/terraform/modules/app/agentcore-identity/tests/tainted-marker/thinkwork-after.tf +3 -0
  106. package/dist/terraform/modules/app/agentcore-identity/tests/tainted-marker/thinkwork-before.tf +3 -0
  107. package/dist/terraform/modules/app/agentcore-identity/variables.tf +6 -0
  108. package/dist/terraform/modules/app/agentcore-memory/README.md +46 -9
  109. package/dist/terraform/modules/app/agentcore-memory/main.tf +27 -10
  110. package/dist/terraform/modules/app/agentcore-memory/scripts/create_or_find_memory.sh +156 -24
  111. package/dist/terraform/modules/app/agentcore-pi/README.md +1 -3
  112. package/dist/terraform/modules/app/agentcore-pi/main.tf +42 -52
  113. package/dist/terraform/modules/app/agentcore-pi/outputs.tf +3 -3
  114. package/dist/terraform/modules/app/agentcore-pi/scripts/reconcile_pi_runtime.mjs +175 -0
  115. package/dist/terraform/modules/app/agentcore-pi/variables.tf +35 -28
  116. package/dist/terraform/modules/app/agentcore-platform/main.tf +68 -0
  117. package/dist/terraform/modules/app/agentcore-runtime/main.tf +4 -20
  118. package/dist/terraform/modules/app/capability-broker/main.tf +2 -2
  119. package/dist/terraform/modules/app/capability-broker/variables.tf +11 -0
  120. package/dist/terraform/modules/app/deployment-control-plane/README.md +264 -0
  121. package/dist/terraform/modules/app/deployment-control-plane/buildspec.yml +4 -1
  122. package/dist/terraform/modules/app/deployment-control-plane/main.tf +94 -0
  123. package/dist/terraform/modules/app/deployment-control-plane/outputs.tf +5 -0
  124. package/dist/terraform/modules/app/deployment-control-plane/runner.py +2641 -124
  125. package/dist/terraform/modules/app/deployment-control-plane/test_runner_bundle.py +2034 -154
  126. package/dist/terraform/modules/app/deployment-control-plane/variables.tf +33 -0
  127. package/dist/terraform/modules/app/lambda-api/auth-state.tf +72 -0
  128. package/dist/terraform/modules/app/lambda-api/chat-latency-observability.tf +466 -0
  129. package/dist/terraform/modules/app/lambda-api/dispatch-dlq.tf +55 -0
  130. package/dist/terraform/modules/app/lambda-api/handlers.tf +411 -723
  131. package/dist/terraform/modules/app/lambda-api/iam-grouped.tf +262 -198
  132. package/dist/terraform/modules/app/lambda-api/main.tf +51 -13
  133. package/dist/terraform/modules/app/lambda-api/mcp-oauth.tf +4 -181
  134. package/dist/terraform/modules/app/lambda-api/memory-alarms.tf +3 -28
  135. package/dist/terraform/modules/app/lambda-api/oauth-secrets.tf +1 -2
  136. package/dist/terraform/modules/app/lambda-api/outputs.tf +17 -45
  137. package/dist/terraform/modules/app/lambda-api/turn-assertion.tf +45 -0
  138. package/dist/terraform/modules/app/lambda-api/variables.tf +219 -126
  139. package/dist/{plugins/n8n/terraform → terraform/modules/app}/n8n/README.md +1 -1
  140. package/dist/{plugins/n8n/terraform → terraform/modules/app}/n8n/main.tf +18 -18
  141. package/dist/terraform/modules/app/sandbox-log-scrubber/README.md +11 -10
  142. package/dist/terraform/modules/app/sandbox-log-scrubber/main.tf +5 -5
  143. package/dist/terraform/modules/app/static-site/main.tf +1 -1
  144. package/dist/terraform/modules/app/www-dns/main.tf +9 -27
  145. package/dist/terraform/modules/app/www-dns/variables.tf +4 -10
  146. package/dist/terraform/modules/data/aurora-postgres/README.md +34 -0
  147. package/dist/terraform/modules/data/aurora-postgres/alarms.tf +82 -0
  148. package/dist/terraform/modules/data/aurora-postgres/main.tf +27 -57
  149. package/dist/terraform/modules/data/aurora-postgres/outputs.tf +0 -9
  150. package/dist/terraform/modules/data/aurora-postgres/variables.tf +41 -0
  151. package/dist/terraform/modules/data/s3-buckets/main.tf +5 -6
  152. package/dist/terraform/modules/foundation/cognito/main.tf +81 -8
  153. package/dist/terraform/modules/foundation/cognito/outputs.tf +5 -0
  154. package/dist/terraform/modules/foundation/cognito/variables.tf +24 -12
  155. package/dist/terraform/modules/thinkwork/README.md +5 -1
  156. package/dist/terraform/modules/thinkwork/main.tf +376 -374
  157. package/dist/terraform/modules/thinkwork/outputs.tf +12 -158
  158. package/dist/terraform/modules/thinkwork/variables.tf +246 -89
  159. package/dist/workspace-defaults/files/INSTRUCTIONS.md +9 -11
  160. package/dist/workspace-defaults/files/MEMORY_GUIDE.md +38 -35
  161. package/dist/workspace-defaults/files/ROUTER.md +1 -1
  162. package/dist/workspace-defaults/files/TOOLS.md +9 -39
  163. package/dist/workspace-defaults/files/agents/analyst/INSTRUCTIONS.md +6 -3
  164. package/dist/workspace-defaults/files/catalog-skills/brain-brief-builder/SKILL.md +165 -0
  165. package/dist/workspace-defaults/files/catalog-skills/n8n-workflow-operator/SKILL.md +75 -0
  166. package/dist/workspace-defaults/files/catalog-skills/n8n-workflow-operator/references/mcp-tooling.md +40 -0
  167. package/dist/workspace-defaults/files/catalog-skills/n8n-workflow-operator/references/validation-and-handoff.md +40 -0
  168. package/dist/workspace-defaults/files/catalog-skills/n8n-workflow-operator/references/workflow-authoring.md +46 -0
  169. package/dist/workspace-defaults/files/skills/artifact-builder/references/crm-dashboard.md +1 -1
  170. package/dist/workspace-defaults/files/skills/document-composer/SKILL.md +22 -3
  171. package/package.json +1 -1
  172. package/dist/drizzle/0261_managed_harness_multiplayer.sql +0 -313
  173. package/dist/drizzle/0262_parallel_harness_threads.sql +0 -9
  174. package/dist/drizzle/0264_harness_tool_execution_ledger.sql +0 -113
  175. package/dist/plugins/company-data/README.md +0 -49
  176. package/dist/plugins/company-data/package.json +0 -21
  177. package/dist/plugins/company-data/src/index.ts +0 -30
  178. package/dist/plugins/company-data/src/manifest.ts +0 -26
  179. package/dist/plugins/company-data/test/manifest.test.ts +0 -116
  180. package/dist/plugins/company-data/tsconfig.json +0 -8
  181. package/dist/plugins/company-etl/README.md +0 -46
  182. package/dist/plugins/company-etl/package.json +0 -21
  183. package/dist/plugins/company-etl/src/index.ts +0 -29
  184. package/dist/plugins/company-etl/src/manifest.ts +0 -26
  185. package/dist/plugins/company-etl/test/manifest.test.ts +0 -108
  186. package/dist/plugins/company-etl/tsconfig.json +0 -8
  187. package/dist/plugins/lastmile/README.md +0 -50
  188. package/dist/plugins/lastmile/package.json +0 -23
  189. package/dist/plugins/lastmile/smoke/lastmile-plugin-smoke.mjs +0 -661
  190. package/dist/plugins/lastmile/src/api/tasks-adapter.ts +0 -572
  191. package/dist/plugins/lastmile/src/discovery.fixture.ts +0 -64
  192. package/dist/plugins/lastmile/src/index.ts +0 -46
  193. package/dist/plugins/lastmile/src/manifest.ts +0 -139
  194. package/dist/plugins/lastmile/test/api/tasks-adapter.test.ts +0 -266
  195. package/dist/plugins/lastmile/test/discovery.test.ts +0 -88
  196. package/dist/plugins/lastmile/tsconfig.json +0 -8
  197. package/dist/plugins/n8n/smoke/n8n-managed-app-smoke.mjs +0 -409
  198. package/dist/plugins/twenty/smoke/twenty-managed-app-smoke.mjs +0 -359
  199. package/dist/terraform/modules/app/agentcore-code-interpreter/Dockerfile.sandbox-base +0 -61
  200. package/dist/terraform/modules/app/agentcore-code-interpreter/sandbox/sitecustomize.py +0 -227
  201. package/dist/terraform/modules/app/agentcore-code-interpreter/sandbox/test_sitecustomize.py +0 -277
  202. package/dist/terraform/modules/app/agentcore-code-interpreter/scripts/build_and_push_sandbox_base.sh +0 -70
  203. package/dist/terraform/modules/app/agentcore-gateway/main.tf +0 -305
  204. package/dist/terraform/modules/app/agentcore-gateway/outputs.tf +0 -44
  205. package/dist/terraform/modules/app/agentcore-gateway/scripts/delete_gateway.sh +0 -93
  206. package/dist/terraform/modules/app/agentcore-gateway/scripts/read_gateway.sh +0 -33
  207. package/dist/terraform/modules/app/agentcore-gateway/scripts/reconcile_gateway.sh +0 -543
  208. package/dist/terraform/modules/app/agentcore-gateway/variables.tf +0 -70
  209. package/dist/terraform/modules/app/agentcore-harness/main.tf +0 -330
  210. package/dist/terraform/modules/app/agentcore-harness/outputs.tf +0 -72
  211. package/dist/terraform/modules/app/agentcore-harness/scripts/delete_harness.sh +0 -5
  212. package/dist/terraform/modules/app/agentcore-harness/scripts/harness-lifecycle.mjs +0 -430
  213. package/dist/terraform/modules/app/agentcore-harness/scripts/harness-readback.mjs +0 -180
  214. package/dist/terraform/modules/app/agentcore-harness/scripts/harness-tool-contract.mjs +0 -193
  215. package/dist/terraform/modules/app/agentcore-harness/scripts/prune_harness.sh +0 -5
  216. package/dist/terraform/modules/app/agentcore-harness/scripts/read_harness.sh +0 -5
  217. package/dist/terraform/modules/app/agentcore-harness/scripts/reconcile_harness.sh +0 -5
  218. package/dist/terraform/modules/app/agentcore-harness/variables.tf +0 -96
  219. package/dist/terraform/modules/app/hindsight-memory/README.md +0 -89
  220. package/dist/terraform/modules/app/hindsight-memory/main.tf +0 -411
  221. package/dist/terraform/modules/app/lambda-api/agentcore-harness-alarms.tf +0 -88
  222. package/dist/terraform/modules/app/lambda-api/agentcore-identity-alarms.tf +0 -91
  223. package/dist/terraform/modules/data/bedrock-knowledge-base/main.tf +0 -102
  224. /package/dist/{plugins/n8n/terraform → terraform/modules/app}/n8n/outputs.tf +0 -0
  225. /package/dist/{plugins/n8n/terraform → terraform/modules/app}/n8n/scripts/sync-database.py +0 -0
  226. /package/dist/{plugins/n8n/terraform → terraform/modules/app}/n8n/scripts/test_sync_database.py +0 -0
  227. /package/dist/{plugins/n8n/terraform → terraform/modules/app}/n8n/variables.tf +0 -0
  228. /package/dist/{plugins/twenty/terraform → terraform/modules/app}/twenty/README.md +0 -0
  229. /package/dist/{plugins/twenty/terraform → terraform/modules/app}/twenty/main.tf +0 -0
  230. /package/dist/{plugins/twenty/terraform → terraform/modules/app}/twenty/outputs.tf +0 -0
  231. /package/dist/{plugins/twenty/terraform → terraform/modules/app}/twenty/variables.tf +0 -0
@@ -1,661 +0,0 @@
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
- }